@active-reach/web-sdk 1.21.0 → 1.22.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.
Files changed (43) hide show
  1. package/dist/aegis-sw.js +1 -1
  2. package/dist/aegis.min.js +1 -1
  3. package/dist/aegis.min.js.map +1 -1
  4. package/dist/{analytics-DblhjFhs.mjs → analytics-0p0p56H0.mjs} +11 -6
  5. package/dist/analytics-0p0p56H0.mjs.map +1 -0
  6. package/dist/chat/AegisChat.d.ts.map +1 -1
  7. package/dist/core/prefetch-bundle-client.d.ts.map +1 -1
  8. package/dist/inapp/AegisInAppManager.d.ts +21 -0
  9. package/dist/inapp/AegisInAppManager.d.ts.map +1 -1
  10. package/dist/inapp/lucide-svg.d.ts +46 -0
  11. package/dist/inapp/lucide-svg.d.ts.map +1 -0
  12. package/dist/inapp/renderers/card-style.d.ts +28 -0
  13. package/dist/inapp/renderers/card-style.d.ts.map +1 -0
  14. package/dist/inapp/renderers/card-template.d.ts +107 -0
  15. package/dist/inapp/renderers/card-template.d.ts.map +1 -0
  16. package/dist/inapp/renderers/carousel-cards.d.ts.map +1 -1
  17. package/dist/inapp/renderers/games.d.ts.map +1 -1
  18. package/dist/inapp/renderers/hero.d.ts.map +1 -1
  19. package/dist/inapp/renderers/index.d.ts +3 -1
  20. package/dist/inapp/renderers/index.d.ts.map +1 -1
  21. package/dist/inapp/renderers/product-recommendation.d.ts +19 -5
  22. package/dist/inapp/renderers/product-recommendation.d.ts.map +1 -1
  23. package/dist/inapp/renderers/progress-bar.d.ts.map +1 -1
  24. package/dist/inapp/renderers/sticky-bar.d.ts.map +1 -1
  25. package/dist/inapp/renderers/stories.d.ts.map +1 -1
  26. package/dist/inapp/renderers/types.d.ts +10 -0
  27. package/dist/inapp/renderers/types.d.ts.map +1 -1
  28. package/dist/inapp/renderers/video.d.ts.map +1 -1
  29. package/dist/index.js +1148 -269
  30. package/dist/index.js.map +1 -1
  31. package/dist/push/AegisWebPush.d.ts +1 -6
  32. package/dist/push/AegisWebPush.d.ts.map +1 -1
  33. package/dist/push/AegisWebPush.js +20 -1
  34. package/dist/push/AegisWebPush.js.map +1 -1
  35. package/dist/react.js +1 -1
  36. package/dist/runtime/AegisMessageRuntime.d.ts +10 -0
  37. package/dist/runtime/AegisMessageRuntime.d.ts.map +1 -1
  38. package/dist/triggers/IntentRuleEvaluator.d.ts +21 -0
  39. package/dist/triggers/IntentRuleEvaluator.d.ts.map +1 -1
  40. package/dist/utils/consent.d.ts.map +1 -1
  41. package/dist/widgets/AegisWidgetManager.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/dist/analytics-DblhjFhs.mjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Storage, l as logger, A as Aegis } from "./analytics-DblhjFhs.mjs";
2
- import { B, E, N, R, U, m } from "./analytics-DblhjFhs.mjs";
1
+ import { S as Storage, l as logger, A as Aegis } from "./analytics-0p0p56H0.mjs";
2
+ import { B, E, N, R, U, m } from "./analytics-0p0p56H0.mjs";
3
3
  import { AegisWebPush } from "./push/AegisWebPush.js";
4
4
  function debounce(func, wait) {
5
5
  let timeoutId = null;
@@ -25,9 +25,700 @@ function throttle(func, limit) {
25
25
  }
26
26
  };
27
27
  }
28
+ const ICONS = {
29
+ x: [
30
+ ["path", { d: "M18 6 6 18" }],
31
+ ["path", { d: "m6 6 12 12" }]
32
+ ],
33
+ check: [["path", { d: "M20 6 9 17l-5-5" }]],
34
+ info: [
35
+ ["circle", { cx: "12", cy: "12", r: "10" }],
36
+ ["path", { d: "M12 16v-4" }],
37
+ ["path", { d: "M12 8h.01" }]
38
+ ],
39
+ "triangle-alert": [
40
+ ["path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }],
41
+ ["path", { d: "M12 9v4" }],
42
+ ["path", { d: "M12 17h.01" }]
43
+ ],
44
+ "volume-2": [
45
+ [
46
+ "path",
47
+ {
48
+ d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"
49
+ }
50
+ ],
51
+ ["path", { d: "M16 9a5 5 0 0 1 0 6" }],
52
+ ["path", { d: "M19.364 18.364a9 9 0 0 0 0-12.728" }]
53
+ ],
54
+ "volume-x": [
55
+ [
56
+ "path",
57
+ {
58
+ d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"
59
+ }
60
+ ],
61
+ ["line", { x1: "22", x2: "16", y1: "9", y2: "15" }],
62
+ ["line", { x1: "16", x2: "22", y1: "9", y2: "15" }]
63
+ ],
64
+ clock: [
65
+ ["circle", { cx: "12", cy: "12", r: "10" }],
66
+ ["polyline", { points: "12 6 12 12 16 14" }]
67
+ ],
68
+ play: [["polygon", { points: "6 3 20 12 6 21 6 3" }]],
69
+ pause: [
70
+ ["rect", { x: "14", y: "4", width: "4", height: "16", rx: "1" }],
71
+ ["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1" }]
72
+ ],
73
+ square: [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }]],
74
+ "square-check": [
75
+ ["path", { d: "M21 10.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h12.5" }],
76
+ ["path", { d: "m9 11 3 3L22 4" }]
77
+ ],
78
+ truck: [
79
+ ["path", { d: "M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2" }],
80
+ ["path", { d: "M15 18H9" }],
81
+ [
82
+ "path",
83
+ {
84
+ d: "M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"
85
+ }
86
+ ],
87
+ ["circle", { cx: "17", cy: "18", r: "2" }],
88
+ ["circle", { cx: "7", cy: "18", r: "2" }]
89
+ ],
90
+ "circle-check": [
91
+ ["circle", { cx: "12", cy: "12", r: "10" }],
92
+ ["path", { d: "m9 12 2 2 4-4" }]
93
+ ],
94
+ // ── Commerce / value-bar set (rich card editor picker) ───────────────────
95
+ "shopping-cart": [
96
+ ["circle", { cx: "8", cy: "21", r: "1" }],
97
+ ["circle", { cx: "19", cy: "21", r: "1" }],
98
+ ["path", { d: "M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12" }]
99
+ ],
100
+ "shopping-bag": [
101
+ ["path", { d: "M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z" }],
102
+ ["path", { d: "M3 6h18" }],
103
+ ["path", { d: "M16 10a4 4 0 0 1-8 0" }]
104
+ ],
105
+ gift: [
106
+ ["rect", { x: "3", y: "8", width: "18", height: "4", rx: "1" }],
107
+ ["path", { d: "M12 8v13" }],
108
+ ["path", { d: "M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7" }],
109
+ ["path", { d: "M7.5 8a2.5 2.5 0 0 1 0-5A4.8 8 0 0 1 12 8a4.8 8 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5" }]
110
+ ],
111
+ tag: [
112
+ ["path", { d: "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z" }],
113
+ ["circle", { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor" }]
114
+ ],
115
+ percent: [
116
+ ["line", { x1: "19", x2: "5", y1: "5", y2: "19" }],
117
+ ["circle", { cx: "6.5", cy: "6.5", r: "2.5" }],
118
+ ["circle", { cx: "17.5", cy: "17.5", r: "2.5" }]
119
+ ],
120
+ star: [
121
+ ["path", { d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" }]
122
+ ],
123
+ sparkles: [
124
+ ["path", { d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" }],
125
+ ["path", { d: "M20 3v4" }],
126
+ ["path", { d: "M22 5h-4" }]
127
+ ],
128
+ zap: [
129
+ ["path", { d: "M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" }]
130
+ ],
131
+ heart: [
132
+ ["path", { d: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" }]
133
+ ],
134
+ bell: [
135
+ ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0" }],
136
+ ["path", { d: "M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" }]
137
+ ],
138
+ lock: [
139
+ ["rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2" }],
140
+ ["path", { d: "M7 11V7a5 5 0 0 1 10 0v4" }]
141
+ ],
142
+ "badge-check": [
143
+ ["path", { d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" }],
144
+ ["path", { d: "m9 12 2 2 4-4" }]
145
+ ],
146
+ package: [
147
+ ["path", { d: "M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z" }],
148
+ ["path", { d: "M12 22V12" }],
149
+ ["path", { d: "m3.3 7 8.7 5 8.7-5" }],
150
+ ["path", { d: "m7.5 4.27 9 5.15" }]
151
+ ],
152
+ flame: [
153
+ ["path", { d: "M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z" }]
154
+ ],
155
+ crown: [
156
+ ["path", { d: "M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z" }],
157
+ ["path", { d: "M5 21h14" }]
158
+ ],
159
+ ticket: [
160
+ ["path", { d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z" }],
161
+ ["path", { d: "M13 5v2" }],
162
+ ["path", { d: "M13 17v2" }],
163
+ ["path", { d: "M13 11v2" }]
164
+ ],
165
+ wallet: [
166
+ ["path", { d: "M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" }],
167
+ ["path", { d: "M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" }]
168
+ ],
169
+ coins: [
170
+ ["circle", { cx: "8", cy: "8", r: "6" }],
171
+ ["path", { d: "M18.09 10.37A6 6 0 1 1 10.34 18" }],
172
+ ["path", { d: "M7 6h1v4" }],
173
+ ["path", { d: "m16.71 13.88.7.71-2.82 2.82" }]
174
+ ],
175
+ trophy: [
176
+ ["path", { d: "M6 9H4.5a2.5 2.5 0 0 1 0-5H6" }],
177
+ ["path", { d: "M18 9h1.5a2.5 2.5 0 0 0 0-5H18" }],
178
+ ["path", { d: "M4 22h16" }],
179
+ ["path", { d: "M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22" }],
180
+ ["path", { d: "M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22" }],
181
+ ["path", { d: "M18 2H6v7a6 6 0 0 0 12 0V2Z" }]
182
+ ],
183
+ timer: [
184
+ ["line", { x1: "10", x2: "14", y1: "2", y2: "2" }],
185
+ ["line", { x1: "12", x2: "15", y1: "14", y2: "11" }],
186
+ ["circle", { cx: "12", cy: "14", r: "8" }]
187
+ ],
188
+ "map-pin": [
189
+ ["path", { d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0" }],
190
+ ["circle", { cx: "12", cy: "10", r: "3" }]
191
+ ],
192
+ user: [
193
+ ["path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }],
194
+ ["circle", { cx: "12", cy: "7", r: "4" }]
195
+ ],
196
+ "thumbs-up": [
197
+ ["path", { d: "M7 10v12" }],
198
+ ["path", { d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" }]
199
+ ],
200
+ "arrow-right": [
201
+ ["path", { d: "M5 12h14" }],
202
+ ["path", { d: "m12 5 7 7-7 7" }]
203
+ ],
204
+ "chevron-right": [["path", { d: "m9 18 6-6-6-6" }]],
205
+ plus: [
206
+ ["path", { d: "M5 12h14" }],
207
+ ["path", { d: "M12 5v14" }]
208
+ ],
209
+ store: [
210
+ ["path", { d: "m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7" }],
211
+ ["path", { d: "M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" }],
212
+ ["path", { d: "M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4" }],
213
+ ["path", { d: "M2 7h20" }],
214
+ ["path", { d: "M22 7v3a2 2 0 0 1-2 2a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7" }]
215
+ ],
216
+ rocket: [
217
+ ["path", { d: "M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z" }],
218
+ ["path", { d: "m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" }],
219
+ ["path", { d: "M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" }],
220
+ ["path", { d: "M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" }]
221
+ ]
222
+ };
223
+ function escapeAttr(value) {
224
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
225
+ }
226
+ const SVG_NS = "http://www.w3.org/2000/svg";
227
+ function lucideSvgEl(name, opts = {}) {
228
+ const node = ICONS[name];
229
+ if (!node || typeof document === "undefined") return null;
230
+ const size = opts.size ?? 24;
231
+ const svg = document.createElementNS(SVG_NS, "svg");
232
+ svg.setAttribute("width", String(size));
233
+ svg.setAttribute("height", String(size));
234
+ svg.setAttribute("viewBox", "0 0 24 24");
235
+ svg.setAttribute("fill", "none");
236
+ svg.setAttribute("stroke", opts.color ?? "currentColor");
237
+ svg.setAttribute("stroke-width", String(opts.strokeWidth ?? 2));
238
+ svg.setAttribute("stroke-linecap", "round");
239
+ svg.setAttribute("stroke-linejoin", "round");
240
+ if (opts.className) svg.setAttribute("class", opts.className);
241
+ if (opts.ariaLabel) {
242
+ svg.setAttribute("role", "img");
243
+ svg.setAttribute("aria-label", opts.ariaLabel);
244
+ } else {
245
+ svg.setAttribute("aria-hidden", "true");
246
+ }
247
+ for (const [tag, attrs] of node) {
248
+ const child = document.createElementNS(SVG_NS, tag);
249
+ for (const [k, v] of Object.entries(attrs)) child.setAttribute(k, String(v));
250
+ svg.appendChild(child);
251
+ }
252
+ return svg;
253
+ }
254
+ function lucideSvg(name, opts = {}) {
255
+ const node = ICONS[name];
256
+ if (!node) return "";
257
+ const size = opts.size ?? 24;
258
+ const color = opts.color ?? "currentColor";
259
+ const strokeWidth = opts.strokeWidth ?? 2;
260
+ const a11y = opts.ariaLabel ? `role="img" aria-label="${escapeAttr(opts.ariaLabel)}"` : 'aria-hidden="true"';
261
+ const cls = opts.className ? ` class="${escapeAttr(opts.className)}"` : "";
262
+ const children = node.map(([tag, attrs]) => {
263
+ const attrStr = Object.entries(attrs).map(([k, v]) => `${k}="${escapeAttr(String(v))}"`).join(" ");
264
+ return `<${tag} ${attrStr}/>`;
265
+ }).join("");
266
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${escapeAttr(color)}" stroke-width="${strokeWidth}" stroke-linecap="round" stroke-linejoin="round"${cls} ${a11y}>${children}</svg>`;
267
+ }
268
+ const WEIGHT = {
269
+ regular: "400",
270
+ medium: "500",
271
+ semibold: "600",
272
+ bold: "700"
273
+ };
274
+ const ICON_PX = { sm: 22, md: 30, lg: 40 };
275
+ const TEXT_PX = { sm: 11.5, md: 13, lg: 15 };
276
+ function interpolateTokens(s, ctx) {
277
+ if (!s || s.indexOf("{") === -1) return s;
278
+ const props = ctx.properties && ctx.properties() || {};
279
+ const cart = ctx.getCartState && ctx.getCartState();
280
+ return s.replace(/\{([a-z0-9_.]+)\}/gi, (_m, key) => {
281
+ let v = props[key];
282
+ if (v == null) {
283
+ if (key === "cart.total") v = cart == null ? void 0 : cart.total;
284
+ else if (key === "cart.count") v = cart == null ? void 0 : cart.itemCount;
285
+ }
286
+ return v == null || v === "" ? "" : String(v);
287
+ });
288
+ }
289
+ const ASPECT$2 = { "1/1": "1 / 1", "4/3": "4 / 3", "16/9": "16 / 9" };
290
+ const DEFAULT_CARD_TEMPLATE = {
291
+ id: "clean",
292
+ frame: { bg: "#ffffff", border: "#f1f1f3", radius: 14 },
293
+ elements: [
294
+ { type: "image", aspect: "1/1", radius: 10 },
295
+ { type: "price", variant: "pill", color: "#059669" },
296
+ { type: "title", lines: 2 },
297
+ { type: "rating" },
298
+ // Zepto/Blinkit-style ADD: a single OUTLINED pill (white fill, brand
299
+ // border + brand text) overlaid on the bottom-right of the product image —
300
+ // NOT a solid filled bar. `overlay:true` anchors it onto the image; the
301
+ // outline variant paints the white/border look. The rail's tile-body click
302
+ // still opens the product, so this one CTA owns add-to-cart.
303
+ { type: "cta", variant: "outline", overlay: true }
304
+ ]
305
+ };
306
+ function asLook(v) {
307
+ return v && typeof v === "object" ? v : {};
308
+ }
309
+ function templateFromCardStyle(look) {
310
+ const elements = [{ type: "image", aspect: "1/1", radius: 10 }];
311
+ if (look.badge) elements.push({ type: "badge", text: look.badge, color: look.badge_color });
312
+ if (!look.hide_price) elements.push({ type: "price", variant: "pill", color: "#059669" });
313
+ elements.push({ type: "title", lines: 2 });
314
+ if (!look.hide_rating) elements.push({ type: "rating" });
315
+ if (!look.hide_cta) elements.push({ type: "cta", variant: "fill", text: look.cta_text });
316
+ return {
317
+ id: "legacy",
318
+ frame: {
319
+ bg: look.background,
320
+ border: look.border_color,
321
+ radius: look.radius,
322
+ accent: look.accent ?? null
323
+ },
324
+ elements
325
+ };
326
+ }
327
+ function resolveGlobalTemplate(ic) {
328
+ const t = ic.card_template;
329
+ if (t && typeof t === "object" && Array.isArray(t.elements)) {
330
+ return t;
331
+ }
332
+ const legacy = ic.card_style;
333
+ if (legacy && typeof legacy === "object") {
334
+ return templateFromCardStyle(asLook(legacy));
335
+ }
336
+ return DEFAULT_CARD_TEMPLATE;
337
+ }
338
+ function applyPerCardOverride(tpl, look) {
339
+ if (!look || Object.keys(look).length === 0) return tpl;
340
+ const frame = { ...tpl.frame || {} };
341
+ if (look.accent) frame.accent = look.accent;
342
+ if (look.radius !== void 0) frame.radius = look.radius;
343
+ let elements = (tpl.elements || []).slice();
344
+ const drop = (type) => elements = elements.filter((e) => e.type !== type);
345
+ if (look.hide_price) drop("price");
346
+ if (look.hide_cta) drop("cta");
347
+ if (look.hide_rating) drop("rating");
348
+ if (look.cta_text) elements = elements.map((e) => e.type === "cta" ? { ...e, text: look.cta_text } : e);
349
+ if (look.badge) {
350
+ const badgeEl = { type: "badge", text: look.badge, color: look.badge_color };
351
+ elements = elements.some((e) => e.type === "badge") ? elements.map((e) => e.type === "badge" ? badgeEl : e) : [elements[0], badgeEl, ...elements.slice(1)].filter(Boolean);
352
+ }
353
+ return { ...tpl, frame, elements };
354
+ }
355
+ function priceText(card) {
356
+ const meta = card.metadata || {};
357
+ const p = meta.price;
358
+ if (p !== void 0 && p !== null) return String(p);
359
+ if (card.body && /\d/.test(card.body)) return card.body;
360
+ return void 0;
361
+ }
362
+ function paintPrice(card, el, accent, ctx) {
363
+ const price = priceText(card);
364
+ if (price === void 0) return null;
365
+ const meta = card.metadata || {};
366
+ const compareAt = meta.compare_at_price ?? meta.compare_at ?? meta.original_price;
367
+ const row = document.createElement("div");
368
+ row.style.cssText = "display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;";
369
+ const p = document.createElement("span");
370
+ p.textContent = price;
371
+ if (el.variant === "plain") {
372
+ p.style.cssText = `color:${accent};font-weight:700;font-size:13px;`;
373
+ } else {
374
+ const pillBg = el.color ? ctx.sanitizeColor(el.color) : "#059669";
375
+ p.style.cssText = `background:${pillBg};color:#fff;font-weight:800;font-size:12px;padding:2px 7px;border-radius:6px;`;
376
+ }
377
+ row.appendChild(p);
378
+ const pNum = parseFloat(price.replace(/[^0-9.]/g, ""));
379
+ const cNum = compareAt !== void 0 ? parseFloat(String(compareAt).replace(/[^0-9.]/g, "")) : NaN;
380
+ if (isFinite(cNum) && isFinite(pNum) && cNum > pNum) {
381
+ const orig = document.createElement("span");
382
+ orig.textContent = String(compareAt);
383
+ orig.style.cssText = "text-decoration:line-through;opacity:0.5;font-size:11px;";
384
+ row.appendChild(orig);
385
+ const off = document.createElement("span");
386
+ off.textContent = `-${Math.round((1 - pNum / cNum) * 100)}%`;
387
+ off.style.cssText = "background:#ef444422;color:#ef4444;font-weight:700;font-size:10px;padding:1px 5px;border-radius:999px;";
388
+ row.appendChild(off);
389
+ }
390
+ return row;
391
+ }
392
+ function paintRating(card) {
393
+ const meta = card.metadata || {};
394
+ const avg = Number(meta.avg_rating ?? 0);
395
+ if (!(avg > 0)) return null;
396
+ const count = Number(meta.review_count ?? 0);
397
+ const rr = document.createElement("div");
398
+ rr.style.cssText = "display:flex;align-items:center;gap:3px;font-size:11px;line-height:1;";
399
+ const star = document.createElement("span");
400
+ star.textContent = "★";
401
+ star.style.cssText = "color:#059669;";
402
+ const val = document.createElement("span");
403
+ val.textContent = avg.toFixed(1);
404
+ val.style.cssText = "color:#059669;font-weight:700;";
405
+ rr.appendChild(star);
406
+ rr.appendChild(val);
407
+ if (count > 0) {
408
+ const c = document.createElement("span");
409
+ c.textContent = `(${count})`;
410
+ c.style.cssText = "color:#94a3b8;";
411
+ rr.appendChild(c);
412
+ }
413
+ return rr;
414
+ }
415
+ function contrastFg(bg) {
416
+ const raw = bg.replace("#", "");
417
+ if (raw.length < 3) return "#1f2937";
418
+ const h = raw.length === 3 ? raw.split("").map((c) => c + c).join("") : raw.slice(0, 6);
419
+ const r = parseInt(h.slice(0, 2), 16);
420
+ const g = parseInt(h.slice(2, 4), 16);
421
+ const b = parseInt(h.slice(4, 6), 16);
422
+ if ([r, g, b].some((n) => Number.isNaN(n))) return "#1f2937";
423
+ const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
424
+ return lum < 0.5 ? "#ffffff" : "#1f2937";
425
+ }
426
+ function buildProgressElement(el, ctx, accent, muted) {
427
+ const { trackEvent, campaign, sanitizeColor } = ctx;
428
+ const goal = el.goal_type || "cart_total";
429
+ const moneyGoal = goal === "cart_total";
430
+ const fill = el.fill ? sanitizeColor(el.fill) : accent;
431
+ const props = () => ctx.properties && ctx.properties() || {};
432
+ const sym = () => {
433
+ var _a;
434
+ if (el.currency_symbol) return el.currency_symbol;
435
+ const code = ctx.getCartState && ((_a = ctx.getCartState()) == null ? void 0 : _a.currency);
436
+ const M = { INR: "₹", USD: "$", EUR: "€", GBP: "£" };
437
+ return code && M[code] || "";
438
+ };
439
+ const threshold = () => {
440
+ if (typeof el.threshold === "number" && el.threshold > 0) return el.threshold;
441
+ if (el.threshold_source === "free_delivery_setting") {
442
+ const v = Number(props()["cart.free_delivery_above"]);
443
+ if (v > 0) return v;
444
+ }
445
+ return 0;
446
+ };
447
+ const readCur = () => {
448
+ const cart = ctx.getCartState && ctx.getCartState();
449
+ if (goal === "items_in_cart") return Number(cart == null ? void 0 : cart.itemCount) || 0;
450
+ if (goal === "cart_total") return Number(cart == null ? void 0 : cart.total) || 0;
451
+ const p = props();
452
+ if (goal === "loyalty_points") return Number(p["loyalty.points"]) || 0;
453
+ if (goal === "referrals") return Number(p["referrals.count"]) || 0;
454
+ return 0;
455
+ };
456
+ const wrap = document.createElement("div");
457
+ wrap.style.cssText = "display:flex;flex-direction:column;gap:3px;min-width:0;width:100%;";
458
+ const sub = document.createElement("div");
459
+ sub.style.cssText = `font-size:11.5px;line-height:1.2;color:${muted};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`;
460
+ const shell = document.createElement("div");
461
+ shell.style.cssText = `height:3px;border-radius:999px;background:color-mix(in srgb, ${fill} 16%, transparent);overflow:hidden;`;
462
+ const bar = document.createElement("div");
463
+ bar.style.cssText = `height:100%;border-radius:999px;background:${fill};width:0%;transition:width .4s cubic-bezier(.4,0,.2,1);`;
464
+ shell.appendChild(bar);
465
+ wrap.appendChild(sub);
466
+ wrap.appendChild(shell);
467
+ let firedUnlock = false;
468
+ const update = () => {
469
+ const th = threshold();
470
+ const cur = readCur();
471
+ const pct = th > 0 ? Math.max(0, Math.min(100, cur / th * 100)) : 0;
472
+ bar.style.width = `${pct}%`;
473
+ if (th > 0 && pct >= 100) {
474
+ sub.textContent = el.reward_text || "Unlocked!";
475
+ sub.style.color = "#34d399";
476
+ if (!firedUnlock) {
477
+ firedUnlock = true;
478
+ trackEvent(campaign.id, "clicked", { stepId: el.id || "unlocked" });
479
+ }
480
+ } else {
481
+ sub.style.color = muted;
482
+ const remaining = Math.max(0, th - cur);
483
+ const pre = moneyGoal ? sym() : "";
484
+ const pctStr = el.show_pct && th > 0 ? ` · ${Math.round(pct)}%` : "";
485
+ sub.textContent = th > 0 ? `Shop for ${pre}${remaining.toFixed(0)} more${pctStr}` : el.reward_text || "";
486
+ }
487
+ };
488
+ update();
489
+ const timer = setInterval(update, 1e3);
490
+ if (typeof window !== "undefined") {
491
+ window.addEventListener("beforeunload", () => clearInterval(timer), { once: true });
492
+ }
493
+ return wrap;
494
+ }
495
+ function renderCardFromTemplate(card, opts) {
496
+ const { ctx, action, layout, layoutAccent } = opts;
497
+ const inline = action === "cart";
498
+ const { campaign, trackEvent, sanitizeUrl, sanitizeColor } = ctx;
499
+ const tpl = applyPerCardOverride(opts.template, asLook(card.style));
500
+ const frame = tpl.frame || {};
501
+ const meta = card.metadata || {};
502
+ const accent = typeof frame.accent === "string" && frame.accent ? sanitizeColor(frame.accent) : layoutAccent;
503
+ const brand = `var(--aegis-brand-accent, ${accent})`;
504
+ const fg = contrastFg(frame.bg ? sanitizeColor(frame.bg) : "#ffffff");
505
+ const muted = fg === "#ffffff" ? "rgba(255,255,255,0.72)" : "#6b7280";
506
+ const tile = document.createElement("div");
507
+ const radius = frame.radius !== void 0 ? `${parseInt(String(frame.radius), 10) || 12}px` : "12px";
508
+ const fLayout = frame.layout;
509
+ const dir = fLayout === "row" ? "row" : "column";
510
+ const tileGap = frame.gap != null ? frame.gap : 6;
511
+ const sizing = fLayout ? frame.width != null ? `width:${typeof frame.width === "number" ? `${frame.width}px` : frame.width};` : "flex:1 1 auto; width:100%;" : layout === "grid" ? "" : "flex:0 0 150px; scroll-snap-align:start;";
512
+ tile.style.cssText = `
513
+ box-sizing:border-box; display:flex; flex-direction:${dir}; ${fLayout === "row" ? "align-items:center;" : ""} gap:${tileGap}px; position:relative;
514
+ color:${fg};
515
+ background:${frame.bg ? sanitizeColor(frame.bg) : "#ffffff"};
516
+ border:1px solid ${frame.border ? sanitizeColor(frame.border) : "transparent"};
517
+ border-radius:${radius}; padding:8px;
518
+ ${frame.max_width ? `max-width:${frame.max_width}px;` : ""}${frame.min_width ? `min-width:${frame.min_width}px;` : ""}
519
+ ${sizing}
520
+ cursor:${card.cta_url || inline ? "pointer" : "default"}; transition:transform 0.15s ease;
521
+ `;
522
+ const elements = tpl.elements && tpl.elements.length ? tpl.elements : DEFAULT_CARD_TEMPLATE.elements;
523
+ let imageWrap = null;
524
+ const paintInto = (container, els) => {
525
+ for (const el of els) {
526
+ switch (el.type) {
527
+ case "image": {
528
+ const wrap = document.createElement("div");
529
+ wrap.style.cssText = `position:relative;width:100%;aspect-ratio:${ASPECT$2[el.aspect || "1/1"] || "1 / 1"};border-radius:${el.radius ?? 10}px;overflow:hidden;background:#f7f8fa;`;
530
+ const safeVideo = card.video_url ? sanitizeUrl(card.video_url) : "";
531
+ if (safeVideo) {
532
+ const v = document.createElement("video");
533
+ v.src = safeVideo;
534
+ v.muted = true;
535
+ v.loop = true;
536
+ v.autoplay = true;
537
+ v.playsInline = true;
538
+ v.setAttribute("playsinline", "");
539
+ v.style.cssText = "width:100%;height:100%;object-fit:cover;display:block;";
540
+ wrap.appendChild(v);
541
+ } else if (card.image_url) {
542
+ const safe = sanitizeUrl(card.image_url);
543
+ if (safe) {
544
+ const img = document.createElement("img");
545
+ img.src = safe;
546
+ img.alt = "";
547
+ img.loading = "lazy";
548
+ img.style.cssText = "width:100%;height:100%;object-fit:cover;display:block;";
549
+ wrap.appendChild(img);
550
+ }
551
+ }
552
+ imageWrap = wrap;
553
+ container.appendChild(wrap);
554
+ break;
555
+ }
556
+ case "badge": {
557
+ const text = el.text;
558
+ if (!text) break;
559
+ const badge = document.createElement("span");
560
+ badge.textContent = text;
561
+ const bg = el.color ? sanitizeColor(el.color) : brand;
562
+ badge.style.cssText = `position:absolute;top:6px;left:6px;z-index:1;background:${bg};color:#fff;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:0.03em;padding:3px 6px;border-radius:5px;`;
563
+ (imageWrap || container).appendChild(badge);
564
+ break;
565
+ }
566
+ case "price": {
567
+ const node = paintPrice(card, el, accent, ctx);
568
+ if (node) container.appendChild(node);
569
+ break;
570
+ }
571
+ case "title": {
572
+ if (!card.title) break;
573
+ const t = document.createElement("div");
574
+ t.textContent = card.title;
575
+ const lines = el.lines && el.lines > 0 ? el.lines : 2;
576
+ t.style.cssText = `font-size:12.5px;font-weight:600;line-height:1.3;text-align:${el.align ?? "left"};color:${el.color ? sanitizeColor(el.color) : fg};display:-webkit-box;-webkit-line-clamp:${lines};-webkit-box-orient:vertical;overflow:hidden;`;
577
+ container.appendChild(t);
578
+ break;
579
+ }
580
+ case "subtitle": {
581
+ if (!card.body) break;
582
+ const s = document.createElement("div");
583
+ s.textContent = card.body;
584
+ const lines = el.lines && el.lines > 0 ? el.lines : 2;
585
+ s.style.cssText = `font-size:11px;color:${el.color ? sanitizeColor(el.color) : muted};line-height:1.3;text-align:${el.align ?? "left"};display:-webkit-box;-webkit-line-clamp:${lines};-webkit-box-orient:vertical;overflow:hidden;`;
586
+ container.appendChild(s);
587
+ break;
588
+ }
589
+ case "rating": {
590
+ const node = paintRating(card);
591
+ if (node) container.appendChild(node);
592
+ break;
593
+ }
594
+ case "category": {
595
+ const cat = meta.category;
596
+ if (typeof cat !== "string" || !cat) break;
597
+ const chip = document.createElement("span");
598
+ chip.textContent = cat;
599
+ chip.style.cssText = "align-self:flex-start;background:#f3f4f6;color:#6b7280;font-size:10px;font-weight:500;padding:2px 7px;border-radius:6px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";
600
+ container.appendChild(chip);
601
+ break;
602
+ }
603
+ // ── rich-card-editor elements (P0) ──────────────────────────────────
604
+ case "icon": {
605
+ const px = ICON_PX[el.size || "md"] || 30;
606
+ const node = document.createElement("div");
607
+ const iconColor = el.color ? sanitizeColor(el.color) : fg;
608
+ if (el.bubble) {
609
+ const bubbleTint = el.color ? sanitizeColor(el.color) : brand;
610
+ node.style.cssText = `flex:0 0 auto;width:${px}px;height:${px}px;border-radius:999px;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb, ${bubbleTint} 16%, transparent);`;
611
+ } else {
612
+ node.style.cssText = "flex:0 0 auto;display:flex;align-items:center;";
613
+ }
614
+ const svg = lucideSvgEl(el.icon_name || "circle-check", {
615
+ size: el.bubble ? Math.round(px * 0.55) : Math.round(px * 0.7),
616
+ color: iconColor,
617
+ strokeWidth: 2
618
+ });
619
+ if (svg) node.appendChild(svg);
620
+ container.appendChild(node);
621
+ break;
622
+ }
623
+ case "text": {
624
+ const t = document.createElement("div");
625
+ const fs = TEXT_PX[el.size || "md"] || 13;
626
+ t.style.cssText = `font-size:${fs}px;font-weight:${WEIGHT[el.weight ?? "medium"]};text-align:${el.align ?? "left"};color:${el.color ? sanitizeColor(el.color) : fg};line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;`;
627
+ t.textContent = interpolateTokens(el.content ?? "", ctx);
628
+ container.appendChild(t);
629
+ break;
630
+ }
631
+ case "spacer": {
632
+ const sp = document.createElement("div");
633
+ sp.style.height = `${el.height ?? 8}px`;
634
+ container.appendChild(sp);
635
+ break;
636
+ }
637
+ case "progress": {
638
+ container.appendChild(buildProgressElement(el, ctx, brand, muted));
639
+ break;
640
+ }
641
+ case "group": {
642
+ const g = document.createElement("div");
643
+ const gdir = el.layout === "row" ? "row" : "column";
644
+ g.style.cssText = `display:flex;flex-direction:${gdir};${el.layout === "row" ? "align-items:center;" : ""}gap:${el.gap ?? 4}px;min-width:0;${el.layout === "row" ? "" : "flex:1 1 auto;"}`;
645
+ paintInto(g, el.elements || []);
646
+ container.appendChild(g);
647
+ break;
648
+ }
649
+ case "cta": {
650
+ const label = el.text || card.cta_text || (inline ? "Add" : "Shop");
651
+ const cta = document.createElement("button");
652
+ cta.className = "aegis-cta";
653
+ cta.textContent = label;
654
+ const overlayOnImage = !!el.overlay && !!imageWrap;
655
+ if (overlayOnImage) {
656
+ cta.style.cssText = el.variant === "outline" ? `position:absolute;right:6px;bottom:6px;background:#fff;color:${brand};border:1px solid ${brand};border-radius:9px;padding:5px 18px;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:0.04em;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.14);` : `position:absolute;right:6px;bottom:6px;background:${brand};color:#fff;border:none;border-radius:9px;padding:5px 18px;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:0.04em;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.14);`;
657
+ } else {
658
+ cta.style.cssText = el.variant === "outline" ? `margin-top:${fLayout === "row" ? "0" : "auto"};background:transparent;color:${brand};border:1px solid ${brand};border-radius:999px;padding:6px 10px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;` : `margin-top:${fLayout === "row" ? "0" : "auto"};background:${brand};color:#fff;border:none;border-radius:999px;padding:7px 10px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;`;
659
+ }
660
+ cta.addEventListener("click", (e) => {
661
+ e.stopPropagation();
662
+ trackEvent(campaign.id, "clicked", { stepId: el.id || "add" });
663
+ if (inline) {
664
+ if (typeof window !== "undefined") {
665
+ window.dispatchEvent(
666
+ new CustomEvent("aegis:add-to-cart", {
667
+ detail: {
668
+ campaign_id: campaign.id,
669
+ product_id: meta.product_id,
670
+ sku: meta.sku,
671
+ title: card.title,
672
+ price: meta.price ?? card.body,
673
+ image_url: card.image_url,
674
+ cta_url: card.cta_url
675
+ }
676
+ })
677
+ );
678
+ }
679
+ return;
680
+ }
681
+ if (card.cta_url) {
682
+ const safe = sanitizeUrl(card.cta_url);
683
+ if (safe) ctx.navigate ? ctx.navigate(safe) : window.location.href = safe;
684
+ }
685
+ });
686
+ (overlayOnImage ? imageWrap : container).appendChild(cta);
687
+ break;
688
+ }
689
+ }
690
+ }
691
+ };
692
+ paintInto(tile, elements);
693
+ const open = (e) => {
694
+ e.stopPropagation();
695
+ trackEvent(campaign.id, "clicked");
696
+ if (inline) {
697
+ if (typeof window !== "undefined") {
698
+ window.dispatchEvent(
699
+ new CustomEvent("aegis:open-product", {
700
+ detail: {
701
+ campaign_id: campaign.id,
702
+ product_id: meta.product_id,
703
+ sku: meta.sku,
704
+ cta_url: card.cta_url
705
+ }
706
+ })
707
+ );
708
+ }
709
+ return;
710
+ }
711
+ if (card.cta_url) {
712
+ const safe = sanitizeUrl(card.cta_url);
713
+ if (safe) ctx.navigate ? ctx.navigate(safe) : window.location.href = safe;
714
+ }
715
+ };
716
+ if (inline || card.cta_url) tile.addEventListener("click", open);
717
+ return tile;
718
+ }
28
719
  const MAX_CARDS_RENDERED = 10;
29
720
  function renderCarouselCards(ctx) {
30
- const { campaign, trackEvent, sanitizeUrl, sanitizeColor, log, addAnimationStyles } = ctx;
721
+ const { campaign, trackEvent, sanitizeColor, log, addAnimationStyles } = ctx;
31
722
  const ic = campaign.interactive_config || {};
32
723
  const rawCards = Array.isArray(ic.cards) ? ic.cards : [];
33
724
  const cards = rawCards.slice(0, MAX_CARDS_RENDERED);
@@ -70,7 +761,7 @@ function renderCarouselCards(ctx) {
70
761
  headerText.appendChild(body);
71
762
  header.appendChild(headerText);
72
763
  const closeBtn = document.createElement("button");
73
- closeBtn.textContent = "";
764
+ closeBtn.innerHTML = lucideSvg("x", { size: 16 });
74
765
  closeBtn.setAttribute("aria-label", "Close");
75
766
  closeBtn.style.cssText = `
76
767
  background: transparent; border: none; color: inherit;
@@ -89,68 +780,16 @@ function renderCarouselCards(ctx) {
89
780
  padding-bottom: 4px; -webkit-overflow-scrolling: touch;
90
781
  `;
91
782
  track.style.msOverflowStyle = "none";
783
+ const template = resolveGlobalTemplate(ic);
92
784
  cards.forEach((c, i) => {
93
- const tile = document.createElement("div");
785
+ const tile = renderCardFromTemplate(c, {
786
+ template,
787
+ ctx,
788
+ action: "navigate",
789
+ layout: "row",
790
+ layoutAccent: accent
791
+ });
94
792
  tile.setAttribute("data-card-index", String(i));
95
- tile.style.cssText = `
96
- flex: 0 0 auto; width: 140px; scroll-snap-align: start;
97
- background: ${fg}0a; border-radius: 12px; padding: 10px;
98
- display: flex; flex-direction: column; gap: 6px;
99
- cursor: ${c.cta_url ? "pointer" : "default"};
100
- `;
101
- const safeVideo = c.video_url ? sanitizeUrl(c.video_url) : "";
102
- if (safeVideo) {
103
- const vid = document.createElement("video");
104
- vid.src = safeVideo;
105
- vid.muted = true;
106
- vid.loop = true;
107
- vid.autoplay = true;
108
- vid.playsInline = true;
109
- vid.setAttribute("playsinline", "");
110
- vid.style.cssText = "width: 100%; height: 96px; border-radius: 8px; object-fit: cover;";
111
- tile.appendChild(vid);
112
- } else if (c.image_url) {
113
- const img = document.createElement("img");
114
- const safe = sanitizeUrl(c.image_url);
115
- if (safe) {
116
- img.src = safe;
117
- img.alt = "";
118
- img.loading = "lazy";
119
- img.style.cssText = "width: 100%; height: 96px; border-radius: 8px; object-fit: cover;";
120
- tile.appendChild(img);
121
- }
122
- }
123
- if (c.title) {
124
- const t = document.createElement("div");
125
- t.textContent = c.title;
126
- t.style.cssText = "font-weight: 600; font-size: 13px; line-height: 1.3;";
127
- tile.appendChild(t);
128
- }
129
- if (c.body) {
130
- const b = document.createElement("div");
131
- b.textContent = c.body;
132
- b.style.cssText = "font-size: 11.5px; opacity: 0.72; line-height: 1.3;";
133
- tile.appendChild(b);
134
- }
135
- if (c.cta_text && c.cta_url) {
136
- const cta = document.createElement("button");
137
- cta.className = "aegis-cta";
138
- cta.textContent = c.cta_text;
139
- cta.style.cssText = `
140
- margin-top: auto; background: ${accent}; color: #fff;
141
- border: none; padding: 6px 10px; border-radius: 999px;
142
- font-size: 12px; font-weight: 600; cursor: pointer;
143
- `;
144
- const goto2 = (e) => {
145
- e.stopPropagation();
146
- trackEvent(campaign.id, "clicked", { stepId: `card_${i}` });
147
- const safe = sanitizeUrl(c.cta_url);
148
- if (safe) window.location.href = safe;
149
- };
150
- cta.addEventListener("click", goto2);
151
- tile.appendChild(cta);
152
- tile.addEventListener("click", goto2);
153
- }
154
793
  track.appendChild(tile);
155
794
  });
156
795
  card.appendChild(track);
@@ -314,7 +953,7 @@ function renderStickyBar(ctx) {
314
953
  };
315
954
  if (dismissible) {
316
955
  const close = document.createElement("button");
317
- close.textContent = "";
956
+ close.innerHTML = lucideSvg("x", { size: 16 });
318
957
  close.setAttribute("aria-label", "Dismiss");
319
958
  close.style.cssText = `
320
959
  background: transparent; border: none; color: inherit;
@@ -344,13 +983,12 @@ function buildProgressBar(ctx, inline) {
344
983
  const source = ic.progress_source === "sse" ? "sse" : "client";
345
984
  addAnimationStyles();
346
985
  const brand = sanitizeColor(campaign.background_color || "#4169e1");
986
+ const accentVar = `var(--aegis-brand-accent, ${brand})`;
347
987
  const branded = ic.surface_style === "branded";
348
- const bg = branded ? brand : "#ffffff";
988
+ const bg = branded ? accentVar : "#ffffff";
349
989
  const fg = sanitizeColor(campaign.text_color || (branded ? "#ffffff" : "#0f172a"));
350
- const fill = sanitizeColor(
351
- ic.progress_fill_color || (branded ? "#ffffff" : brand)
352
- );
353
- const track = branded ? "rgba(255,255,255,0.25)" : `${fill}22`;
990
+ const fill = ic.progress_fill_color ? sanitizeColor(ic.progress_fill_color) : branded ? "#ffffff" : accentVar;
991
+ const track = branded ? "rgba(255,255,255,0.25)" : `color-mix(in srgb, ${fill} 14%, transparent)`;
354
992
  const bar = document.createElement("div");
355
993
  bar.className = "aegis-in-app-progress-bar";
356
994
  bar.setAttribute("data-campaign-id", campaign.id);
@@ -730,7 +1368,7 @@ function renderCoachmarkTour(ctx) {
730
1368
  }
731
1369
  const MAX_PRODUCTS = 24;
732
1370
  function renderProductRecommendation(ctx) {
733
- const { campaign, trackEvent, sanitizeUrl, sanitizeColor, log, addAnimationStyles } = ctx;
1371
+ const { campaign, trackEvent, sanitizeColor, log, addAnimationStyles } = ctx;
734
1372
  const ic = campaign.interactive_config || {};
735
1373
  const rawCards = Array.isArray(ic.cards) ? ic.cards : [];
736
1374
  const cards = rawCards.slice(0, MAX_PRODUCTS);
@@ -739,7 +1377,7 @@ function renderProductRecommendation(ctx) {
739
1377
  return;
740
1378
  }
741
1379
  const layout = ic.rec_layout || "grid";
742
- const ctaDefault = ic.rec_cta_text || "Shop now";
1380
+ const template = resolveGlobalTemplate(ic);
743
1381
  addAnimationStyles();
744
1382
  const bg = "#ffffff";
745
1383
  const fg = "#0f172a";
@@ -782,7 +1420,7 @@ function renderProductRecommendation(ctx) {
782
1420
  headerText.appendChild(body);
783
1421
  header.appendChild(headerText);
784
1422
  const close = document.createElement("button");
785
- close.textContent = "";
1423
+ close.innerHTML = lucideSvg("x", { size: 18 });
786
1424
  close.setAttribute("aria-label", "Close");
787
1425
  close.style.cssText = `
788
1426
  background: transparent; border: none; color: inherit;
@@ -807,82 +1445,9 @@ function renderProductRecommendation(ctx) {
807
1445
  `;
808
1446
  }
809
1447
  cards.forEach((c) => {
810
- const tile = document.createElement("div");
811
- const isRow = layout === "row";
812
- tile.style.cssText = `
813
- background: ${fg}08; border-radius: 12px;
814
- padding: 10px; display: flex; flex-direction: column; gap: 6px;
815
- cursor: ${c.cta_url ? "pointer" : "default"};
816
- ${isRow ? "flex: 0 0 150px; scroll-snap-align: start;" : ""}
817
- transition: transform 0.15s ease;
818
- `;
819
- if (c.image_url) {
820
- const img = document.createElement("img");
821
- const safe = sanitizeUrl(c.image_url);
822
- if (safe) {
823
- img.src = safe;
824
- img.alt = "";
825
- img.loading = "lazy";
826
- img.style.cssText = "width: 100%; aspect-ratio: 1 / 1; border-radius: 8px; object-fit: cover;";
827
- tile.appendChild(img);
828
- }
829
- }
830
- if (c.title) {
831
- const t = document.createElement("div");
832
- t.textContent = c.title;
833
- t.style.cssText = "font-weight: 600; font-size: 13px; line-height: 1.3;";
834
- tile.appendChild(t);
835
- }
836
- const meta = c.metadata && typeof c.metadata === "object" ? c.metadata : {};
837
- const price = meta.price;
838
- const compareAt = meta.compare_at_price ?? meta.compare_at ?? meta.original_price;
839
- if (price !== void 0) {
840
- const priceRow = document.createElement("div");
841
- priceRow.style.cssText = "display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;";
842
- const p = document.createElement("span");
843
- p.textContent = String(price);
844
- p.style.cssText = `color: ${accent}; font-weight: 700; font-size: 13px;`;
845
- priceRow.appendChild(p);
846
- const pNum = parseFloat(String(price).replace(/[^0-9.]/g, ""));
847
- const cNum = compareAt !== void 0 ? parseFloat(String(compareAt).replace(/[^0-9.]/g, "")) : NaN;
848
- if (isFinite(cNum) && isFinite(pNum) && cNum > pNum) {
849
- const orig = document.createElement("span");
850
- orig.textContent = String(compareAt);
851
- orig.style.cssText = "text-decoration: line-through; opacity: 0.5; font-size: 11px;";
852
- priceRow.appendChild(orig);
853
- const off = document.createElement("span");
854
- off.textContent = `-${Math.round((1 - pNum / cNum) * 100)}%`;
855
- off.style.cssText = "background: #ef444422; color: #ef4444; font-weight: 700; font-size: 10px; padding: 1px 5px; border-radius: 999px;";
856
- priceRow.appendChild(off);
857
- }
858
- tile.appendChild(priceRow);
859
- } else if (c.body) {
860
- const b = document.createElement("div");
861
- b.textContent = c.body;
862
- b.style.cssText = "font-size: 11.5px; opacity: 0.72; line-height: 1.3;";
863
- tile.appendChild(b);
864
- }
865
- const cta = document.createElement("button");
866
- cta.className = "aegis-cta";
867
- cta.textContent = c.cta_text || ctaDefault;
868
- cta.style.cssText = `
869
- margin-top: auto;
870
- background: ${accent}; color: #fff;
871
- border: none; padding: 7px 10px; border-radius: 999px;
872
- font-size: 12px; font-weight: 600; cursor: pointer;
873
- `;
874
- const go = (e) => {
875
- e.stopPropagation();
876
- trackEvent(campaign.id, "clicked");
877
- if (c.cta_url) {
878
- const safe = sanitizeUrl(c.cta_url);
879
- if (safe) window.location.href = safe;
880
- }
881
- };
882
- cta.addEventListener("click", go);
883
- tile.appendChild(cta);
884
- if (c.cta_url) tile.addEventListener("click", go);
885
- grid.appendChild(tile);
1448
+ grid.appendChild(
1449
+ renderCardFromTemplate(c, { template, ctx, action: "navigate", layout, layoutAccent: accent })
1450
+ );
886
1451
  });
887
1452
  sheet.appendChild(grid);
888
1453
  overlay.appendChild(sheet);
@@ -894,6 +1459,88 @@ function renderProductRecommendation(ctx) {
894
1459
  });
895
1460
  document.body.appendChild(overlay);
896
1461
  }
1462
+ function renderProductRecommendationInline(ctx, target) {
1463
+ const { campaign, sanitizeColor, addAnimationStyles, log } = ctx;
1464
+ const ic = campaign.interactive_config || {};
1465
+ const rawCards = Array.isArray(ic.cards) ? ic.cards : [];
1466
+ const cards = rawCards.slice(0, MAX_PRODUCTS);
1467
+ if (cards.length === 0) {
1468
+ log("product_recommendation (inline) rendered with zero products — skipping", "warn");
1469
+ return false;
1470
+ }
1471
+ const layout = ic.rec_layout || "row";
1472
+ const template = resolveGlobalTemplate(ic);
1473
+ const fg = "#0f172a";
1474
+ const accent = sanitizeColor(campaign.background_color || "#4169e1");
1475
+ addAnimationStyles();
1476
+ const section = document.createElement("section");
1477
+ section.className = "aegis-in-app-product-rec-inline";
1478
+ section.setAttribute("data-campaign-id", campaign.id);
1479
+ section.style.cssText = `
1480
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1481
+ color: ${fg};
1482
+ `;
1483
+ if (campaign.title) {
1484
+ const title = document.createElement("h2");
1485
+ title.textContent = campaign.title;
1486
+ title.style.cssText = "font-weight: 700; font-size: 16px; margin: 0 0 2px;";
1487
+ section.appendChild(title);
1488
+ }
1489
+ if (campaign.body) {
1490
+ const body = document.createElement("div");
1491
+ body.textContent = campaign.body;
1492
+ body.style.cssText = "font-size: 13px; opacity: 0.65; line-height: 1.4; margin-bottom: 10px;";
1493
+ section.appendChild(body);
1494
+ }
1495
+ const grid = document.createElement("div");
1496
+ const isRow = layout !== "grid";
1497
+ if (layout === "grid") {
1498
+ grid.style.cssText = "display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;";
1499
+ } else {
1500
+ grid.style.cssText = `
1501
+ display: flex; gap: 10px; overflow-x: auto;
1502
+ scroll-snap-type: x proximity; padding: 0 0 6px;
1503
+ -webkit-overflow-scrolling: touch; scrollbar-width: none;
1504
+ `;
1505
+ grid.style.msOverflowStyle = "none";
1506
+ }
1507
+ cards.forEach((c) => {
1508
+ grid.appendChild(
1509
+ renderCardFromTemplate(c, { template, ctx, action: "cart", layout, layoutAccent: accent })
1510
+ );
1511
+ });
1512
+ if (isRow && cards.length > 2) {
1513
+ const wrap = document.createElement("div");
1514
+ wrap.style.cssText = "position: relative;";
1515
+ wrap.appendChild(grid);
1516
+ const arrow = document.createElement("button");
1517
+ arrow.setAttribute("aria-label", "Scroll for more");
1518
+ arrow.innerHTML = "›";
1519
+ arrow.style.cssText = `
1520
+ position: absolute; top: 50%; right: 0; transform: translateY(-50%);
1521
+ width: 30px; height: 30px; border-radius: 999px; border: 1px solid ${fg}1a;
1522
+ background: #fff; color: ${fg}; font-size: 18px; line-height: 1; cursor: pointer;
1523
+ box-shadow: 0 2px 8px rgba(0,0,0,0.12); display: flex; align-items: center;
1524
+ justify-content: center; z-index: 2;
1525
+ `;
1526
+ arrow.addEventListener("click", (e) => {
1527
+ e.stopPropagation();
1528
+ grid.scrollBy({ left: Math.round(grid.clientWidth * 0.8), behavior: "smooth" });
1529
+ });
1530
+ const syncArrow = () => {
1531
+ const atEnd = grid.scrollLeft + grid.clientWidth >= grid.scrollWidth - 8;
1532
+ arrow.style.display = atEnd ? "none" : "flex";
1533
+ };
1534
+ grid.addEventListener("scroll", syncArrow, { passive: true });
1535
+ setTimeout(syncArrow, 0);
1536
+ wrap.appendChild(arrow);
1537
+ section.appendChild(wrap);
1538
+ } else {
1539
+ section.appendChild(grid);
1540
+ }
1541
+ target.appendChild(section);
1542
+ return true;
1543
+ }
897
1544
  const MAX_GROUPS = 12;
898
1545
  const MAX_FRAMES = 20;
899
1546
  const DEFAULT_DURATION_MS = 5e3;
@@ -997,7 +1644,7 @@ function renderLauncherBar(ctx, cfg, target) {
997
1644
  if (floating) {
998
1645
  const dismiss = document.createElement("button");
999
1646
  dismiss.type = "button";
1000
- dismiss.textContent = "";
1647
+ dismiss.innerHTML = lucideSvg("x", { size: 14 });
1001
1648
  dismiss.setAttribute("aria-label", "Dismiss stories");
1002
1649
  dismiss.style.cssText = `
1003
1650
  flex: 0 0 auto; align-self: center; margin-left: auto;
@@ -1141,7 +1788,7 @@ function openViewer(ctx, cfg, startGroupIndex, startFrameIndex = 0) {
1141
1788
  };
1142
1789
  const close = document.createElement("button");
1143
1790
  close.type = "button";
1144
- close.textContent = "";
1791
+ close.innerHTML = lucideSvg("x", { size: 18 });
1145
1792
  close.setAttribute("aria-label", "Close");
1146
1793
  close.style.cssText = "position: absolute; top: 12px; right: 10px; z-index: 5; width: 34px; height: 34px; border: none; border-radius: 999px; background: rgba(0,0,0,0.32); color: #fff; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; line-height: 1;";
1147
1794
  stage.appendChild(close);
@@ -1418,7 +2065,7 @@ function openGameModal(ctx, className, maxWidth = 360) {
1418
2065
  };
1419
2066
  const close = document.createElement("button");
1420
2067
  close.type = "button";
1421
- close.textContent = "";
2068
+ close.innerHTML = lucideSvg("x", { size: 15 });
1422
2069
  close.setAttribute("aria-label", "Close");
1423
2070
  close.style.cssText = `position:absolute;top:10px;right:10px;width:30px;height:30px;border:none;border-radius:999px;background:${branded ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.05)"};color:${branded ? "#fff" : "#475569"};font-size:15px;cursor:pointer;z-index:1;`;
1424
2071
  close.addEventListener("click", () => dismiss(true));
@@ -2136,14 +2783,15 @@ function buildVideo(ctx, aspect, autoplay, loop) {
2136
2783
  });
2137
2784
  const pill = document.createElement("button");
2138
2785
  pill.type = "button";
2139
- pill.textContent = "🔇";
2786
+ pill.innerHTML = lucideSvg("volume-x", { size: 14 });
2140
2787
  pill.setAttribute("aria-label", "Unmute");
2141
2788
  pill.style.cssText = "position:absolute;bottom:10px;right:10px;z-index:2;width:34px;height:34px;border:none;border-radius:999px;background:rgba(0,0,0,0.45);color:#fff;font-size:14px;cursor:pointer;";
2142
2789
  pill.addEventListener("click", (e) => {
2143
2790
  var _a2;
2144
2791
  e.stopPropagation();
2145
2792
  vid.muted = !vid.muted;
2146
- pill.textContent = vid.muted ? "🔇" : "🔊";
2793
+ pill.innerHTML = lucideSvg(vid.muted ? "volume-x" : "volume-2", { size: 14 });
2794
+ pill.setAttribute("aria-label", vid.muted ? "Unmute" : "Mute");
2147
2795
  if (!vid.muted) void ((_a2 = vid.play) == null ? void 0 : _a2.call(vid).catch(() => {
2148
2796
  }));
2149
2797
  });
@@ -2231,7 +2879,7 @@ function renderVideo(ctx) {
2231
2879
  }
2232
2880
  const close = document.createElement("button");
2233
2881
  close.type = "button";
2234
- close.textContent = "";
2882
+ close.innerHTML = lucideSvg("x", { size: 15 });
2235
2883
  close.setAttribute("aria-label", "Close");
2236
2884
  close.style.cssText = "position:absolute;top:10px;left:10px;z-index:3;width:32px;height:32px;border:none;border-radius:999px;background:rgba(0,0,0,0.4);color:#fff;font-size:15px;cursor:pointer;";
2237
2885
  close.addEventListener("click", () => {
@@ -2276,8 +2924,15 @@ const HEX = /^#[0-9a-fA-F]{3,8}$/;
2276
2924
  function safeColor(c, fallback) {
2277
2925
  return c && HEX.test(c) ? c : fallback;
2278
2926
  }
2279
- const RADIUS = { none: "0", lg: "12px", xl: "16px", "2xl": "20px" };
2280
2927
  const ASPECT = { "16:9": "16 / 9", "4:3": "4 / 3", "21:9": "21 / 9" };
2928
+ function _injectHeroResponsiveCss() {
2929
+ if (typeof document === "undefined") return;
2930
+ if (document.getElementById("aegis-hero-style")) return;
2931
+ const style = document.createElement("style");
2932
+ style.id = "aegis-hero-style";
2933
+ style.textContent = ".aegis-hero-root{border-radius:16px;}@media (min-width:768px){.aegis-hero-root{border-radius:0;min-height:45vh;max-height:70vh;}}";
2934
+ document.head.appendChild(style);
2935
+ }
2281
2936
  function cardHeadline(c) {
2282
2937
  return c.headline ?? c.title ?? "";
2283
2938
  }
@@ -2303,7 +2958,6 @@ function renderHeroInline(ctx, target) {
2303
2958
  chrome.accent && chrome.accent !== "brand" ? chrome.accent : campaign.background_color,
2304
2959
  "#4169e1"
2305
2960
  );
2306
- const radius = RADIUS[chrome.radius ?? "2xl"] ?? RADIUS["2xl"];
2307
2961
  const variant = chrome.variant ?? "hero_fullbleed";
2308
2962
  if (variant === "announcement_bar") {
2309
2963
  const c = cards[0];
@@ -2326,8 +2980,10 @@ function renderHeroInline(ctx, target) {
2326
2980
  trackEvent(campaign.id, "impression");
2327
2981
  return true;
2328
2982
  }
2983
+ _injectHeroResponsiveCss();
2329
2984
  const root = document.createElement("div");
2330
- root.style.cssText = `position:relative;width:100%;overflow:hidden;border-radius:${radius};box-shadow:0 1px 2px rgba(16,24,40,0.06);${chrome.aspect_ratio && ASPECT[chrome.aspect_ratio] ? `aspect-ratio:${ASPECT[chrome.aspect_ratio]};` : "min-height:300px;"}`;
2985
+ root.className = "aegis-hero-root";
2986
+ root.style.cssText = `position:relative;width:100%;overflow:hidden;box-shadow:0 1px 2px rgba(16,24,40,0.06);${chrome.aspect_ratio && ASPECT[chrome.aspect_ratio] ? `aspect-ratio:${ASPECT[chrome.aspect_ratio]};` : "min-height:300px;"}`;
2331
2987
  root.setAttribute("data-campaign-id", campaign.id);
2332
2988
  const track = document.createElement("div");
2333
2989
  track.style.cssText = "display:flex;height:100%;width:100%;transition:transform 0.5s ease;";
@@ -2750,7 +3406,7 @@ class AegisChat {
2750
3406
  const closeBtn = document.createElement("button");
2751
3407
  closeBtn.className = "aegis-chat-close";
2752
3408
  closeBtn.setAttribute("aria-label", "Close chat");
2753
- closeBtn.textContent = "";
3409
+ closeBtn.innerHTML = lucideSvg("x", { size: 16 });
2754
3410
  closeBtn.addEventListener("click", () => this.closePanel());
2755
3411
  header.appendChild(titleEl);
2756
3412
  header.appendChild(closeBtn);
@@ -2806,7 +3462,7 @@ class AegisChat {
2806
3462
  const tipClose = document.createElement("button");
2807
3463
  tipClose.className = "aegis-chat-tip-close";
2808
3464
  tipClose.setAttribute("aria-label", "Dismiss");
2809
- tipClose.textContent = "";
3465
+ tipClose.innerHTML = lucideSvg("x", { size: 14 });
2810
3466
  tipClose.addEventListener("click", (e) => {
2811
3467
  e.stopPropagation();
2812
3468
  this.dismissAttention();
@@ -3772,10 +4428,15 @@ const _AegisInAppManager = class _AegisInAppManager {
3772
4428
  this.ready = new Promise((resolve) => {
3773
4429
  this.readyResolve = resolve;
3774
4430
  });
4431
+ this.pendingPreInitRefresh = false;
3775
4432
  this.currentSurface = null;
3776
4433
  this.currentScreen = null;
3777
4434
  this.currentLocationId = null;
3778
4435
  this.filledSlots = /* @__PURE__ */ new WeakSet();
4436
+ this.slotSignatures = /* @__PURE__ */ new WeakMap();
4437
+ this.slotTimers = /* @__PURE__ */ new WeakMap();
4438
+ this.slotObserver = null;
4439
+ this.slotRenderScheduled = false;
3779
4440
  this.writeKey = config.writeKey;
3780
4441
  this.apiHost = config.apiHost || "https://api.aegis.ai";
3781
4442
  this.userId = config.userId ?? readAnonIdFromStorage$1();
@@ -3887,6 +4548,11 @@ const _AegisInAppManager = class _AegisInAppManager {
3887
4548
  this.connectSSE();
3888
4549
  }
3889
4550
  this.isInitialized = true;
4551
+ if (this.pendingPreInitRefresh) {
4552
+ this.pendingPreInitRefresh = false;
4553
+ this.log("Re-fetching campaigns for surface/screen declared during init");
4554
+ void this.refreshCampaigns();
4555
+ }
3890
4556
  this.log("AegisInApp initialized successfully");
3891
4557
  }
3892
4558
  updateUserId(userId) {
@@ -3935,16 +4601,17 @@ const _AegisInAppManager = class _AegisInAppManager {
3935
4601
  * change `currentSurface`, so arbitrary events never poison the surface gate.
3936
4602
  */
3937
4603
  refreshOnEvent(eventName, isScreenDeclaration = false) {
3938
- if (!this.isInitialized) {
3939
- this.log(`refreshOnEvent(${eventName}) before initialize — ignored`);
3940
- return;
3941
- }
3942
4604
  if (eventName && _AegisInAppManager.KNOWN_SURFACES.has(eventName)) {
3943
4605
  this.currentSurface = eventName;
3944
4606
  }
3945
4607
  if (isScreenDeclaration && eventName && eventName !== "page_view") {
3946
4608
  this.currentScreen = eventName;
3947
4609
  }
4610
+ if (!this.isInitialized) {
4611
+ this.pendingPreInitRefresh = true;
4612
+ this.log(`refreshOnEvent(${eventName}) before initialize — surface captured, refetch deferred to initialize()`);
4613
+ return;
4614
+ }
3948
4615
  if (this.refreshDebounceTimer) {
3949
4616
  clearTimeout(this.refreshDebounceTimer);
3950
4617
  }
@@ -4162,7 +4829,7 @@ const _AegisInAppManager = class _AegisInAppManager {
4162
4829
  try {
4163
4830
  const context = new URLSearchParams({
4164
4831
  device_type: this.detectDeviceType(),
4165
- page_url: typeof window !== "undefined" ? window.location.pathname : "/"
4832
+ page_url: typeof window !== "undefined" ? window.location.pathname + window.location.search : "/"
4166
4833
  });
4167
4834
  context.set("is_new_user", this.isNewUser() ? "true" : "false");
4168
4835
  if (this.currentSurface) {
@@ -4320,28 +4987,75 @@ const _AegisInAppManager = class _AegisInAppManager {
4320
4987
  if (!c.surface || c.surface.length === 0) return screenOk;
4321
4988
  return screenOk || this.matchesCurrentSurface(c);
4322
4989
  }
4990
+ // Re-fill slots that mount after a poll (SPA route changes). Observes the DOM
4991
+ // once; when a new [data-aegis-slot] appears it re-runs renderIntoSlots from
4992
+ // the cached campaigns (idempotent — already-filled slots are skipped). Does
4993
+ // NOT loop on the SDK's own injections (those add content INTO a slot, not a
4994
+ // new slot element).
4995
+ ensureSlotObserver() {
4996
+ if (this.slotObserver || typeof MutationObserver === "undefined" || typeof document === "undefined") {
4997
+ return;
4998
+ }
4999
+ this.slotObserver = new MutationObserver((mutations) => {
5000
+ let sawSlot = false;
5001
+ for (const m2 of mutations) {
5002
+ m2.addedNodes.forEach((n) => {
5003
+ var _a, _b;
5004
+ if (sawSlot || n.nodeType !== 1) return;
5005
+ const el = n;
5006
+ if (((_a = el.matches) == null ? void 0 : _a.call(el, "[data-aegis-slot]")) || ((_b = el.querySelector) == null ? void 0 : _b.call(el, "[data-aegis-slot]"))) {
5007
+ sawSlot = true;
5008
+ }
5009
+ });
5010
+ if (sawSlot) break;
5011
+ }
5012
+ if (!sawSlot || this.slotRenderScheduled) return;
5013
+ this.slotRenderScheduled = true;
5014
+ const run = () => {
5015
+ this.slotRenderScheduled = false;
5016
+ this.renderIntoSlots();
5017
+ };
5018
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
5019
+ else setTimeout(run, 0);
5020
+ });
5021
+ this.slotObserver.observe(document.body, { childList: true, subtree: true });
5022
+ }
4323
5023
  renderIntoSlots() {
4324
5024
  if (typeof document === "undefined") return;
5025
+ this.ensureSlotObserver();
4325
5026
  const slots = document.querySelectorAll("[data-aegis-slot]");
4326
5027
  if (slots.length === 0) return;
4327
5028
  const eligibleByCategory = /* @__PURE__ */ new Map();
5029
+ const seenIds = /* @__PURE__ */ new Set();
4328
5030
  for (const c of this.campaigns) {
4329
5031
  if (!this.matchesCurrentScreenOrSurface(c)) continue;
4330
5032
  const modes = c.delivery_modes;
4331
5033
  const category = c.widget_category;
4332
5034
  if (!modes || !modes.includes("embedded_card")) continue;
4333
5035
  if (!category) continue;
5036
+ if (seenIds.has(c.id)) continue;
5037
+ seenIds.add(c.id);
4334
5038
  const arr2 = eligibleByCategory.get(category);
4335
5039
  if (arr2) arr2.push(c);
4336
5040
  else eligibleByCategory.set(category, [c]);
4337
5041
  }
4338
5042
  if (eligibleByCategory.size === 0) return;
4339
5043
  slots.forEach((slot) => {
4340
- if (this.filledSlots.has(slot)) return;
4341
5044
  const key = slot.getAttribute("data-aegis-slot");
4342
5045
  if (!key) return;
4343
5046
  const list = eligibleByCategory.get(key);
4344
5047
  if (!list || list.length === 0) return;
5048
+ const sig = list.map((c) => `${c.id}:${c.assigned_variant_id ?? ""}`).join(",");
5049
+ if (this.filledSlots.has(slot) && this.slotSignatures.get(slot) === sig) return;
5050
+ const oldTimer = this.slotTimers.get(slot);
5051
+ if (oldTimer) {
5052
+ clearInterval(oldTimer);
5053
+ this.slotTimers.delete(slot);
5054
+ }
5055
+ slot.querySelectorAll(
5056
+ ":scope > [data-aegis-rotate-pane], :scope > [data-aegis-slot-rendered], :scope > [data-aegis-rotate-dots]"
5057
+ ).forEach((n) => n.remove());
5058
+ this.slotSignatures.set(slot, sig);
4345
5059
  slot.querySelectorAll(":scope > [data-aegis-slot-default]").forEach((d) => d.remove());
4346
5060
  const attrMs = parseInt(slot.getAttribute("data-aegis-slot-rotate") || "0", 10);
4347
5061
  const declaredMs = list.reduce((m2, c) => {
@@ -4350,10 +5064,15 @@ const _AegisInAppManager = class _AegisInAppManager {
4350
5064
  return Number.isFinite(v) && v > m2 ? v : m2;
4351
5065
  }, 0);
4352
5066
  const rotateMs = attrMs > 0 ? attrMs : declaredMs;
5067
+ const flush = slot.hasAttribute("data-aegis-slot-flush");
4353
5068
  if (rotateMs > 0 && list.length > 1) {
4354
- this.renderRotatingSlot(slot, list, rotateMs);
5069
+ this.renderRotatingSlot(slot, list, rotateMs, flush);
4355
5070
  } else {
4356
- this.renderCampaignIntoSlot(list[0], slot);
5071
+ const host = document.createElement("div");
5072
+ host.setAttribute("data-aegis-slot-rendered", list[0].id);
5073
+ slot.appendChild(host);
5074
+ this.renderCampaignIntoSlot(list[0], host);
5075
+ if (flush) this._flattenSlotCard(host);
4357
5076
  }
4358
5077
  this.filledSlots.add(slot);
4359
5078
  });
@@ -4366,7 +5085,7 @@ const _AegisInAppManager = class _AegisInAppManager {
4366
5085
  * is deferred at render and fired per campaign on FIRST visibility, so hidden
4367
5086
  * panes never over-count. One rendered pane → no rotation (just shows it).
4368
5087
  */
4369
- renderRotatingSlot(slot, campaigns, rotateMs) {
5088
+ renderRotatingSlot(slot, campaigns, rotateMs, flush = false) {
4370
5089
  const panes = [];
4371
5090
  for (const c of campaigns) {
4372
5091
  const pane = document.createElement("div");
@@ -4374,8 +5093,10 @@ const _AegisInAppManager = class _AegisInAppManager {
4374
5093
  pane.style.display = "none";
4375
5094
  slot.appendChild(pane);
4376
5095
  this.renderCampaignIntoSlot(c, pane, { deferImpression: true });
4377
- if (pane.childElementCount > 0) panes.push({ el: pane, campaign: c });
4378
- else pane.remove();
5096
+ if (pane.childElementCount > 0) {
5097
+ if (flush) this._flattenSlotCard(pane);
5098
+ panes.push({ el: pane, campaign: c });
5099
+ } else pane.remove();
4379
5100
  }
4380
5101
  if (panes.length === 0) return;
4381
5102
  const impressed = /* @__PURE__ */ new Set();
@@ -4386,17 +5107,59 @@ const _AegisInAppManager = class _AegisInAppManager {
4386
5107
  this.emit("campaign-shown", c);
4387
5108
  };
4388
5109
  let idx = 0;
4389
- panes[0].el.style.display = "";
4390
- markVisible(panes[0].campaign);
4391
- if (panes.length === 1) return;
4392
- const timer = setInterval(() => {
5110
+ const dots = [];
5111
+ if (panes.length > 1) {
5112
+ const bar = document.createElement("div");
5113
+ bar.setAttribute("data-aegis-rotate-dots", "");
5114
+ bar.setAttribute("role", "tablist");
5115
+ bar.setAttribute("aria-label", "Offers");
5116
+ bar.style.cssText = "display:flex;align-items:center;justify-content:center;gap:5px;padding:2px 0 8px;";
5117
+ panes.forEach((_, i) => {
5118
+ const dot = document.createElement("button");
5119
+ dot.type = "button";
5120
+ dot.setAttribute("role", "tab");
5121
+ dot.setAttribute("aria-label", `Offer ${i + 1} of ${panes.length}`);
5122
+ dot.style.cssText = "border:none;padding:0;cursor:pointer;height:6px;width:6px;border-radius:999px;background:rgba(15,23,42,0.2);transition:width .25s ease,background .25s ease;";
5123
+ dot.addEventListener("click", (e) => {
5124
+ e.stopPropagation();
5125
+ show(i, true);
5126
+ });
5127
+ dots.push(dot);
5128
+ bar.appendChild(dot);
5129
+ });
5130
+ slot.appendChild(bar);
5131
+ }
5132
+ const paintDots = () => {
5133
+ dots.forEach((d, i) => {
5134
+ const active = i === idx;
5135
+ d.style.width = active ? "16px" : "6px";
5136
+ d.style.background = active ? "var(--aegis-brand-accent, #4169e1)" : "rgba(15,23,42,0.2)";
5137
+ d.setAttribute("aria-selected", active ? "true" : "false");
5138
+ });
5139
+ };
5140
+ let timer = null;
5141
+ const start = () => setInterval(() => {
5142
+ show(idx + 1);
5143
+ }, rotateMs);
5144
+ function show(i, fromClick = false) {
4393
5145
  panes[idx].el.style.display = "none";
4394
- idx = (idx + 1) % panes.length;
5146
+ idx = (i % panes.length + panes.length) % panes.length;
4395
5147
  panes[idx].el.style.display = "";
4396
5148
  markVisible(panes[idx].campaign);
4397
- }, rotateMs);
5149
+ paintDots();
5150
+ if (fromClick && timer) {
5151
+ clearInterval(timer);
5152
+ timer = start();
5153
+ }
5154
+ }
5155
+ panes[0].el.style.display = "";
5156
+ markVisible(panes[0].campaign);
5157
+ paintDots();
5158
+ if (panes.length === 1) return;
5159
+ timer = start();
5160
+ this.slotTimers.set(slot, timer);
4398
5161
  if (typeof window !== "undefined") {
4399
- window.addEventListener("beforeunload", () => clearInterval(timer), { once: true });
5162
+ window.addEventListener("beforeunload", () => timer && clearInterval(timer), { once: true });
4400
5163
  }
4401
5164
  }
4402
5165
  /**
@@ -4450,6 +5213,14 @@ const _AegisInAppManager = class _AegisInAppManager {
4450
5213
  target.querySelectorAll(":scope > [data-aegis-slot-default]").forEach((d) => d.remove());
4451
5214
  }
4452
5215
  }
5216
+ const spacing = Number(anchor == null ? void 0 : anchor.element_spacing);
5217
+ if (spacing > 0) {
5218
+ if (position === "before" || position === "after") {
5219
+ target.style.margin = `${spacing}px`;
5220
+ } else {
5221
+ target.style.padding = `${spacing}px`;
5222
+ }
5223
+ }
4453
5224
  if (this.filledSlots.has(target)) continue;
4454
5225
  this.renderCampaignIntoSlot(c, target);
4455
5226
  this.filledSlots.add(target);
@@ -4555,7 +5326,12 @@ const _AegisInAppManager = class _AegisInAppManager {
4555
5326
  }
4556
5327
  return;
4557
5328
  }
4558
- if (campaign.widget_category === "hero") {
5329
+ const _tpl = ic.card_template;
5330
+ const _hasComposedTemplate = !!_tpl && typeof _tpl === "object" && Array.isArray(_tpl.elements);
5331
+ const _isMultiCard = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
5332
+ if (_hasComposedTemplate && !_isMultiCard) {
5333
+ rendered = this.renderComposedCardSlot(campaign, ic, target);
5334
+ } else if (campaign.widget_category === "hero") {
4559
5335
  rendered = renderHeroInline(this.buildRenderContext(campaign), target);
4560
5336
  } else
4561
5337
  switch (campaign.sub_type) {
@@ -4608,6 +5384,9 @@ const _AegisInAppManager = class _AegisInAppManager {
4608
5384
  case "progress_bar":
4609
5385
  rendered = renderProgressBarInline(this.buildRenderContext(campaign), target);
4610
5386
  break;
5387
+ case "product_recommendation":
5388
+ rendered = renderProductRecommendationInline(this.buildRenderContext(campaign), target);
5389
+ break;
4611
5390
  case "quiz":
4612
5391
  rendered = this.renderQuizSlot(campaign, ic, bg, text, target);
4613
5392
  if (!rendered) {
@@ -4670,6 +5449,34 @@ const _AegisInAppManager = class _AegisInAppManager {
4670
5449
  * The catch-all for banner + nudge sub_types (birthday, referral, app-install,
4671
5450
  * win-back, etc.) so EVERY gallery nudge renders on the bill (previously only
4672
5451
  * star_rating / nps_survey / form were handled → nudges never appeared). */
5452
+ /**
5453
+ * Composed single card — renders a campaign's top-level `card_template`
5454
+ * (frame + elements: icon/text/progress/image/cta/group/…) through the generic
5455
+ * `renderCardFromTemplate` engine. The canonical path for the rich card editor:
5456
+ * the storefront value bar, loyalty nudges, and any operator-composed card all
5457
+ * render here from declarative JSON, on ACS / custom / third-party sites.
5458
+ */
5459
+ renderComposedCardSlot(campaign, ic, target) {
5460
+ const c = campaign;
5461
+ const card = {
5462
+ image_url: typeof c.image_url === "string" ? c.image_url : void 0,
5463
+ title: campaign.title,
5464
+ body: campaign.body,
5465
+ cta_text: typeof c.button_text === "string" ? c.button_text : void 0,
5466
+ cta_url: typeof c.action_url === "string" ? c.action_url : void 0,
5467
+ metadata: ic.card_meta || {}
5468
+ };
5469
+ const template = resolveGlobalTemplate(ic);
5470
+ const { accent } = this._surfacePalette(campaign);
5471
+ const tile = renderCardFromTemplate(card, {
5472
+ template,
5473
+ ctx: this.buildRenderContext(campaign),
5474
+ action: "navigate",
5475
+ layoutAccent: this.sanitizeColor(campaign.background_color || accent)
5476
+ });
5477
+ target.appendChild(tile);
5478
+ return true;
5479
+ }
4673
5480
  renderGenericCardSlot(campaign, ic, bg, text, target) {
4674
5481
  const st = this._slotStyle(campaign);
4675
5482
  const card = this._wrapInSlotCard("aegis-in-app-generic-card", campaign.id, bg, text, st.radius);
@@ -4685,6 +5492,45 @@ const _AegisInAppManager = class _AegisInAppManager {
4685
5492
  im.style.cssText = "width: 100%; height: 96px; object-fit: cover; display: block;";
4686
5493
  card.appendChild(im);
4687
5494
  }
5495
+ const widgetCat = typeof c.widget_category === "string" ? c.widget_category : "";
5496
+ if (widgetCat === "promo" && !img) {
5497
+ const row = document.createElement("div");
5498
+ row.style.cssText = `display:flex; align-items:center; gap:12px; min-height:46px; padding:${st.padY ?? 10}px ${st.padX ?? 14}px;`;
5499
+ const colL = document.createElement("div");
5500
+ colL.style.cssText = "flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px;";
5501
+ if (campaign.title) {
5502
+ const t = document.createElement("div");
5503
+ t.style.cssText = `font-size:13px; font-weight:${st.weight ?? "700"}; line-height:1.25; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;`;
5504
+ t.textContent = campaign.title;
5505
+ colL.appendChild(t);
5506
+ }
5507
+ if (campaign.body) {
5508
+ const b = document.createElement("div");
5509
+ b.style.cssText = "font-size:11.5px; opacity:0.7; line-height:1.3; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;";
5510
+ b.textContent = campaign.body;
5511
+ colL.appendChild(b);
5512
+ }
5513
+ row.appendChild(colL);
5514
+ if (buttonText) {
5515
+ const btn = document.createElement("button");
5516
+ btn.type = "button";
5517
+ btn.textContent = buttonText;
5518
+ const accentTok = `var(--aegis-brand-accent, ${text})`;
5519
+ btn.style.cssText = `flex:0 0 auto; align-self:center; cursor:pointer; white-space:nowrap;background:color-mix(in srgb, ${accentTok} 12%, #fff); color:${accentTok}; border:none;border-radius:999px; padding:7px 16px; font-size:12.5px; font-weight:700;`;
5520
+ btn.addEventListener("click", () => {
5521
+ void this.trackEvent(campaign.id, "clicked");
5522
+ if (actionUrl) {
5523
+ window.open(actionUrl, "_blank", "noopener");
5524
+ } else if (typeof window !== "undefined") {
5525
+ window.dispatchEvent(new CustomEvent("aegis:open-login", { detail: { campaign_id: campaign.id } }));
5526
+ }
5527
+ });
5528
+ row.appendChild(btn);
5529
+ }
5530
+ card.appendChild(row);
5531
+ target.appendChild(card);
5532
+ return true;
5533
+ }
4688
5534
  const cbody = document.createElement("div");
4689
5535
  cbody.style.cssText = `padding: ${st.padY ?? 12}px ${st.padX ?? 12}px; display: flex; flex-direction: column; gap: 6px;`;
4690
5536
  if (campaign.title) {
@@ -4828,8 +5674,14 @@ const _AegisInAppManager = class _AegisInAppManager {
4828
5674
  body.style.alignItems = "center";
4829
5675
  body.style.justifyContent = "center";
4830
5676
  const done = document.createElement("div");
4831
- done.style.cssText = "padding: 8px 0; font-size: 13px; font-weight: 600; text-align: center;";
4832
- done.textContent = typeof ((_a = campaign.interactive_config) == null ? void 0 : _a.thank_you_message) === "string" ? campaign.interactive_config.thank_you_message : "✓ Thanks — your details are saved.";
5677
+ done.style.cssText = "padding: 8px 0; font-size: 13px; font-weight: 600; text-align: center; display: inline-flex; align-items: center; justify-content: center; gap: 6px;";
5678
+ const customThanks = typeof ((_a = campaign.interactive_config) == null ? void 0 : _a.thank_you_message) === "string" ? campaign.interactive_config.thank_you_message : null;
5679
+ if (customThanks) {
5680
+ done.textContent = customThanks;
5681
+ } else {
5682
+ done.innerHTML = lucideSvg("check", { size: 14 });
5683
+ done.appendChild(document.createTextNode("Thanks — your details are saved."));
5684
+ }
4833
5685
  body.appendChild(done);
4834
5686
  this._playReaction(body, "affirm", "check.json", campaign.interactive_config);
4835
5687
  });
@@ -5169,11 +6021,11 @@ const _AegisInAppManager = class _AegisInAppManager {
5169
6021
  const soundBtn = document.createElement("button");
5170
6022
  soundBtn.type = "button";
5171
6023
  soundBtn.setAttribute("aria-label", "Toggle sound");
5172
- soundBtn.textContent = soundOn ? "🔊" : "🔇";
6024
+ soundBtn.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
5173
6025
  soundBtn.style.cssText = "position: absolute; top: 10px; left: 12px; background: transparent; border: none; font-size: 15px; cursor: pointer; opacity: 0.65; z-index: 6; line-height: 1;";
5174
6026
  soundBtn.addEventListener("click", () => {
5175
6027
  soundOn = !soundOn;
5176
- soundBtn.textContent = soundOn ? "🔊" : "🔇";
6028
+ soundBtn.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
5177
6029
  });
5178
6030
  card.appendChild(soundBtn);
5179
6031
  }
@@ -5288,7 +6140,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5288
6140
  var _a;
5289
6141
  try {
5290
6142
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(r.code));
5291
- chip.textContent = "Copied";
6143
+ chip.textContent = "Copied";
5292
6144
  } catch {
5293
6145
  }
5294
6146
  });
@@ -5334,7 +6186,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5334
6186
  var _a;
5335
6187
  try {
5336
6188
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(r.code));
5337
- chip.textContent = "Copied";
6189
+ chip.textContent = "Copied";
5338
6190
  } catch {
5339
6191
  }
5340
6192
  });
@@ -5575,7 +6427,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5575
6427
  pctLabel.textContent = `${pct}%`;
5576
6428
  pctLabel.style.opacity = "0.8";
5577
6429
  } else if (pctLabel && i === selected) {
5578
- pctLabel.textContent = "";
6430
+ pctLabel.innerHTML = lucideSvg("check", { size: 14 });
5579
6431
  pctLabel.style.opacity = "0.8";
5580
6432
  }
5581
6433
  });
@@ -5598,59 +6450,18 @@ const _AegisInAppManager = class _AegisInAppManager {
5598
6450
  }
5599
6451
  const strip = document.createElement("div");
5600
6452
  strip.style.cssText = `display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory; padding: ${st.padY ?? 12}px ${st.padX ?? 12}px;`;
6453
+ const template = resolveGlobalTemplate(ic);
6454
+ const ctx = this.buildRenderContext(campaign);
6455
+ const accent = this.sanitizeColor(campaign.background_color || text);
5601
6456
  cards.forEach((c, i) => {
5602
- const tile = document.createElement("div");
5603
- const ctaUrl = typeof c.cta_url === "string" ? c.cta_url : "";
5604
- tile.style.cssText = `flex: 0 0 auto; width: 150px; scroll-snap-align: start; background: ${text}0a; border-radius: 10px; overflow: hidden; display: flex; flex-direction: column; gap: 6px; cursor: ${ctaUrl ? "pointer" : "default"};`;
5605
- const videoUrl = typeof c.video_url === "string" ? c.video_url : "";
5606
- const imageUrl = typeof c.image_url === "string" ? c.image_url : "";
5607
- if (videoUrl) {
5608
- const v = document.createElement("video");
5609
- v.src = videoUrl;
5610
- v.muted = true;
5611
- v.loop = true;
5612
- v.autoplay = true;
5613
- v.playsInline = true;
5614
- v.setAttribute("playsinline", "");
5615
- v.style.cssText = "width: 100%; height: 90px; object-fit: cover; display: block;";
5616
- tile.appendChild(v);
5617
- } else if (imageUrl) {
5618
- const im = document.createElement("img");
5619
- im.src = imageUrl;
5620
- im.alt = "";
5621
- im.loading = "lazy";
5622
- im.style.cssText = "width: 100%; height: 90px; object-fit: cover; display: block;";
5623
- tile.appendChild(im);
5624
- }
5625
- const tb = document.createElement("div");
5626
- tb.style.cssText = "padding: 0 8px 8px; display: flex; flex-direction: column; gap: 4px;";
5627
- if (typeof c.title === "string" && c.title) {
5628
- const tt = document.createElement("div");
5629
- tt.style.cssText = "font-size: 12px; font-weight: 600; line-height: 1.3;";
5630
- tt.textContent = c.title;
5631
- tb.appendChild(tt);
5632
- }
5633
- if (typeof c.body === "string" && c.body) {
5634
- const tbd = document.createElement("div");
5635
- tbd.style.cssText = "font-size: 10.5px; opacity: 0.72; line-height: 1.3;";
5636
- tbd.textContent = c.body;
5637
- tb.appendChild(tbd);
5638
- }
5639
- const ctaText = typeof c.cta_text === "string" ? c.cta_text : "";
5640
- if (ctaText && ctaUrl) {
5641
- const cta = document.createElement("button");
5642
- cta.textContent = ctaText;
5643
- cta.style.cssText = `margin-top: auto; align-self: flex-start; background: ${text}; color: ${bg}; border: none; padding: 5px 10px; border-radius: ${st.btnRadius ?? 999}px; font-size: 11px; font-weight: 600; cursor: pointer;`;
5644
- const goto = (e) => {
5645
- e.stopPropagation();
5646
- void this.trackEvent(campaign.id, "clicked", { stepId: `card_${i}` });
5647
- window.open(ctaUrl, "_blank", "noopener");
5648
- };
5649
- cta.addEventListener("click", goto);
5650
- tile.addEventListener("click", goto);
5651
- tb.appendChild(cta);
5652
- }
5653
- tile.appendChild(tb);
6457
+ const tile = renderCardFromTemplate(c, {
6458
+ template,
6459
+ ctx,
6460
+ action: "navigate",
6461
+ layout: "row",
6462
+ layoutAccent: accent
6463
+ });
6464
+ tile.setAttribute("data-card-index", String(i));
5654
6465
  strip.appendChild(tile);
5655
6466
  });
5656
6467
  card.appendChild(strip);
@@ -5902,11 +6713,11 @@ const _AegisInAppManager = class _AegisInAppManager {
5902
6713
  const sb = document.createElement("button");
5903
6714
  sb.type = "button";
5904
6715
  sb.setAttribute("aria-label", "Toggle sound");
5905
- sb.textContent = soundOn ? "🔊" : "🔇";
6716
+ sb.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
5906
6717
  sb.style.cssText = "position: absolute; top: 10px; left: 12px; background: transparent; border: none; font-size: 15px; cursor: pointer; opacity: 0.65; z-index: 6; line-height: 1;";
5907
6718
  sb.addEventListener("click", () => {
5908
6719
  soundOn = !soundOn;
5909
- sb.textContent = soundOn ? "🔊" : "🔇";
6720
+ sb.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
5910
6721
  });
5911
6722
  card.appendChild(sb);
5912
6723
  }
@@ -6075,7 +6886,7 @@ const _AegisInAppManager = class _AegisInAppManager {
6075
6886
  var _a;
6076
6887
  try {
6077
6888
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(code));
6078
- chip.textContent = "Copied";
6889
+ chip.textContent = "Copied";
6079
6890
  } catch {
6080
6891
  }
6081
6892
  });
@@ -6263,6 +7074,19 @@ const _AegisInAppManager = class _AegisInAppManager {
6263
7074
  `;
6264
7075
  return card;
6265
7076
  }
7077
+ /**
7078
+ * Flush a rendered slot card into its host card — drop the per-campaign radius
7079
+ * + shadow so it reads as one unit with the host wrapper (used for the
7080
+ * storefront value bar via `data-aegis-slot-flush`). Targets every
7081
+ * `[data-campaign-id]` card inside `container` (or the container itself).
7082
+ */
7083
+ _flattenSlotCard(container) {
7084
+ const cards = container.matches("[data-campaign-id]") ? [container] : Array.from(container.querySelectorAll("[data-campaign-id]"));
7085
+ for (const el of cards) {
7086
+ el.style.borderRadius = "0";
7087
+ el.style.boxShadow = "none";
7088
+ }
7089
+ }
6266
7090
  // Optional header/brand image atop a survey-style widget (star / nps).
6267
7091
  _appendHeaderImage(body, ic) {
6268
7092
  const url = ic.header_image_url;
@@ -6704,6 +7528,9 @@ const _AegisInAppManager = class _AegisInAppManager {
6704
7528
  }
6705
7529
  }
6706
7530
  displayCampaign(campaign) {
7531
+ if (Array.isArray(campaign.delivery_modes) && campaign.delivery_modes.length > 0 && !campaign.delivery_modes.includes("in_app_overlay")) {
7532
+ return;
7533
+ }
6707
7534
  const proceed = this.emit("campaign-will-show", campaign);
6708
7535
  if (!proceed) {
6709
7536
  this.log(`campaign ${campaign.id} suppressed by campaign-will-show handler`);
@@ -6833,6 +7660,16 @@ const _AegisInAppManager = class _AegisInAppManager {
6833
7660
  return {
6834
7661
  campaign,
6835
7662
  getCartState: this.getCartState,
7663
+ properties: () => {
7664
+ var _a, _b;
7665
+ const cart = (_a = this.getCartState) == null ? void 0 : _a.call(this);
7666
+ const ctxObj = ((_b = campaign.interactive_config) == null ? void 0 : _b.card_context) || {};
7667
+ return {
7668
+ "cart.total": cart == null ? void 0 : cart.total,
7669
+ "cart.count": cart == null ? void 0 : cart.itemCount,
7670
+ ...ctxObj
7671
+ };
7672
+ },
6836
7673
  trackEvent: (id, evt, extra) => {
6837
7674
  void this.trackEvent(id, evt, extra);
6838
7675
  },
@@ -6943,7 +7780,7 @@ const _AegisInAppManager = class _AegisInAppManager {
6943
7780
  '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>'
6944
7781
  );
6945
7782
  if (closeIcon) x.appendChild(closeIcon);
6946
- else x.textContent = "";
7783
+ else x.innerHTML = lucideSvg("x", { size: 14 });
6947
7784
  x.style.cssText = `
6948
7785
  position: absolute; top: 12px; right: 12px; z-index: 4; width: 28px; height: 28px;
6949
7786
  display: flex; align-items: center; justify-content: center; border: none;
@@ -7781,12 +8618,17 @@ const _AegisInAppManager = class _AegisInAppManager {
7781
8618
  _surfacePalette(campaign) {
7782
8619
  const ic = campaign.interactive_config || {};
7783
8620
  const brand = this.sanitizeColor(campaign.background_color || "#4169e1");
7784
- const branded = ic.surface_style === "branded";
7785
- if (branded) {
8621
+ const surface = ic.surface_style;
8622
+ if (surface === "branded") {
7786
8623
  const bg = brand;
7787
8624
  const text = campaign.text_color ? this.sanitizeColor(campaign.text_color) : "#ffffff";
7788
8625
  return { bg, text, btnBg: text, btnText: bg, accent: text, branded: true };
7789
8626
  }
8627
+ if (surface === "dark") {
8628
+ const bg = this.sanitizeColor(campaign.background_color || "#0f172a");
8629
+ const text = campaign.text_color ? this.sanitizeColor(campaign.text_color) : "#ffffff";
8630
+ return { bg, text, btnBg: "#ffffff", btnText: bg, accent: text, branded: false };
8631
+ }
7790
8632
  return { bg: "#ffffff", text: "#0f172a", btnBg: brand, btnText: this._contrastText(brand), accent: brand, branded: false };
7791
8633
  }
7792
8634
  inAppStyle(campaign) {
@@ -7890,7 +8732,7 @@ const _AegisInAppManager = class _AegisInAppManager {
7890
8732
  actionsContainer.appendChild(ctaButton);
7891
8733
  }
7892
8734
  const closeButton = document.createElement("button");
7893
- closeButton.textContent = "";
8735
+ closeButton.innerHTML = lucideSvg("x", { size: 18 });
7894
8736
  closeButton.setAttribute("aria-label", "Close");
7895
8737
  closeButton.style.cssText = `
7896
8738
  background: transparent;
@@ -8088,7 +8930,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8088
8930
  pill.replaceChildren();
8089
8931
  const ic = document.createElement("span");
8090
8932
  ic.style.fontSize = "13px";
8091
- ic.textContent = v.muted ? "🔇" : "🔊";
8933
+ ic.innerHTML = lucideSvg(v.muted ? "volume-x" : "volume-2", { size: 13 });
8092
8934
  pill.appendChild(ic);
8093
8935
  if (v.muted) {
8094
8936
  const t = document.createElement("span");
@@ -8218,7 +9060,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8218
9060
  }
8219
9061
  overlay.appendChild(contentContainer);
8220
9062
  const closeButton = document.createElement("button");
8221
- closeButton.textContent = "";
9063
+ closeButton.innerHTML = lucideSvg("x", { size: isBg ? 18 : 28 });
8222
9064
  closeButton.setAttribute("aria-label", "Close");
8223
9065
  closeButton.style.cssText = `
8224
9066
  position: absolute; top: 20px; right: 20px; z-index: 3; background: ${isBg ? "rgba(0,0,0,0.45)" : "transparent"};
@@ -8346,7 +9188,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8346
9188
  }
8347
9189
  modal.appendChild(content);
8348
9190
  const closeButton = document.createElement("button");
8349
- closeButton.textContent = "";
9191
+ closeButton.innerHTML = lucideSvg("x", { size: 22 });
8350
9192
  closeButton.setAttribute("aria-label", "Close");
8351
9193
  closeButton.style.cssText = `
8352
9194
  position: absolute;
@@ -8419,16 +9261,16 @@ const _AegisInAppManager = class _AegisInAppManager {
8419
9261
  const ic = campaign.interactive_config || {};
8420
9262
  const variant = ic.alert_variant || "";
8421
9263
  const VARIANTS = {
8422
- info: { glyph: "", color: "#3b82f6" },
8423
- success: { glyph: "", color: "#10b981" },
8424
- warning: { glyph: "!", color: "#f59e0b" },
8425
- error: { glyph: "", color: "#ef4444" }
9264
+ info: { icon: "info", color: "#3b82f6" },
9265
+ success: { icon: "check", color: "#10b981" },
9266
+ warning: { icon: "triangle-alert", color: "#f59e0b" },
9267
+ error: { icon: "x", color: "#ef4444" }
8426
9268
  };
8427
9269
  const vspec = VARIANTS[variant];
8428
9270
  if (vspec) {
8429
9271
  const badge = document.createElement("div");
8430
9272
  badge.style.cssText = `width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: 800; background: ${vspec.color}22; color: ${vspec.color};`;
8431
- badge.textContent = vspec.glyph;
9273
+ badge.innerHTML = lucideSvg(vspec.icon, { size: 24 });
8432
9274
  content.appendChild(badge);
8433
9275
  }
8434
9276
  const title = document.createElement("h3");
@@ -8570,7 +9412,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8570
9412
  if (!hasMedia) {
8571
9413
  const placeholder = document.createElement("div");
8572
9414
  placeholder.style.cssText = "width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #1f2937, #0f172a); color: rgba(255,255,255,0.35); font-size: 34px;";
8573
- placeholder.textContent = "";
9415
+ placeholder.innerHTML = lucideSvg("play", { size: 34 });
8574
9416
  mediaWrap.appendChild(placeholder);
8575
9417
  }
8576
9418
  pip.appendChild(mediaWrap);
@@ -8581,7 +9423,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8581
9423
  playGlyph.setAttribute("aria-label", "Play or pause");
8582
9424
  playGlyph.style.cssText = "position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 52px; height: 52px; border-radius: 50%; border: none; background: rgba(0,0,0,0.45); color: #fff; font-size: 22px; cursor: pointer; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.25s ease; backdrop-filter: blur(2px);";
8583
9425
  const syncGlyph = () => {
8584
- playGlyph.textContent = v.paused ? "" : "❚❚";
9426
+ playGlyph.innerHTML = lucideSvg(v.paused ? "play" : "pause", { size: 22 });
8585
9427
  playGlyph.style.opacity = v.paused ? "1" : "0";
8586
9428
  };
8587
9429
  const togglePlay = () => {
@@ -8608,7 +9450,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8608
9450
  mutePill.replaceChildren();
8609
9451
  const ic2 = document.createElement("span");
8610
9452
  ic2.style.fontSize = "13px";
8611
- ic2.textContent = v.muted ? "🔇" : "🔊";
9453
+ ic2.innerHTML = lucideSvg(v.muted ? "volume-x" : "volume-2", { size: 13 });
8612
9454
  mutePill.appendChild(ic2);
8613
9455
  if (v.muted) {
8614
9456
  const t = document.createElement("span");
@@ -8703,7 +9545,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8703
9545
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
8704
9546
  `;
8705
9547
  if (expandBtn) {
8706
- expandBtn.textContent = "";
9548
+ expandBtn.innerHTML = lucideSvg("x", { size: 16 });
8707
9549
  expandBtn.setAttribute("aria-label", "Minimize");
8708
9550
  }
8709
9551
  if (video && video.muted) {
@@ -8726,7 +9568,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8726
9568
  }
8727
9569
  const closeButton = document.createElement("button");
8728
9570
  closeButton.type = "button";
8729
- closeButton.textContent = "";
9571
+ closeButton.innerHTML = lucideSvg("x", { size: 16 });
8730
9572
  closeButton.setAttribute("aria-label", "Close");
8731
9573
  closeButton.style.cssText = ctrlBtnCss;
8732
9574
  closeButton.addEventListener("click", (e) => {
@@ -9746,13 +10588,29 @@ function evaluateRule(rule, snapshot, namespaces) {
9746
10588
  const result = evaluateExpr(rule.when, snapshot, namespaces);
9747
10589
  return result === true;
9748
10590
  }
9749
- class IntentRuleEvaluator {
10591
+ const _IntentRuleEvaluator = class _IntentRuleEvaluator {
9750
10592
  constructor() {
9751
10593
  this.armed = [];
9752
10594
  this.snapshot = {};
9753
10595
  this.namespaces = {};
9754
10596
  this.firedThisSession = /* @__PURE__ */ new Set();
9755
10597
  this.silencedThisSession = /* @__PURE__ */ new Set();
10598
+ this.oneInterruptionPerSession = false;
10599
+ this.interruptiveBudgetSpent = false;
10600
+ this.ttfiMs = 0;
10601
+ this.sessionStartMs = Date.now();
10602
+ this.nowFn = () => Date.now();
10603
+ }
10604
+ /** Tune governance. `now` also (re)bases the TTFI session clock. */
10605
+ setGovernance(opts) {
10606
+ if (opts.oneInterruptionPerSession !== void 0) {
10607
+ this.oneInterruptionPerSession = opts.oneInterruptionPerSession;
10608
+ }
10609
+ if (opts.ttfiMs !== void 0) this.ttfiMs = opts.ttfiMs;
10610
+ if (opts.now) {
10611
+ this.nowFn = opts.now;
10612
+ this.sessionStartMs = opts.now();
10613
+ }
9756
10614
  }
9757
10615
  // ─── snapshot management ───
9758
10616
  updateSignal(signal, value) {
@@ -9788,12 +10646,15 @@ class IntentRuleEvaluator {
9788
10646
  }
9789
10647
  markFired(campaignId) {
9790
10648
  this.firedThisSession.add(campaignId);
10649
+ this.interruptiveBudgetSpent = true;
9791
10650
  }
9792
10651
  /** Clear all per-session state. Caller invokes on logout or explicit
9793
10652
  * session reset. */
9794
10653
  reset() {
9795
10654
  this.firedThisSession.clear();
9796
10655
  this.silencedThisSession.clear();
10656
+ this.interruptiveBudgetSpent = false;
10657
+ this.sessionStartMs = this.nowFn();
9797
10658
  }
9798
10659
  // ─── the main dispatch ───
9799
10660
  /**
@@ -9816,6 +10677,11 @@ class IntentRuleEvaluator {
9816
10677
  * 5. If nothing matches, return 'none'.
9817
10678
  */
9818
10679
  onSignalChanged(signal) {
10680
+ if (this.oneInterruptionPerSession && this.interruptiveBudgetSpent) {
10681
+ return { kind: "none" };
10682
+ }
10683
+ const sessionElapsedMs = this.nowFn() - this.sessionStartMs;
10684
+ const exemptFromTtfi = _IntentRuleEvaluator.EXIT_SIGNALS.includes(signal);
9819
10685
  const candidates = this.armed.filter((c) => c.rule.fire_on.includes(signal)).filter((c) => !this.firedThisSession.has(c.id)).filter((c) => !this.silencedThisSession.has(c.id)).sort((a, b) => {
9820
10686
  if (b.rule.priority !== a.rule.priority) {
9821
10687
  return b.rule.priority - a.rule.priority;
@@ -9825,6 +10691,9 @@ class IntentRuleEvaluator {
9825
10691
  for (let i = 0; i < candidates.length; i++) {
9826
10692
  const c = candidates[i];
9827
10693
  if (!evaluateRule(c.rule, this.snapshot, this.namespaces)) continue;
10694
+ if (this.ttfiMs > 0 && sessionElapsedMs < this.ttfiMs && !exemptFromTtfi) {
10695
+ continue;
10696
+ }
9828
10697
  if (c.rule.suppress_competing) {
9829
10698
  const suppressIds = this.armed.filter((other) => other.id !== c.id).map((other) => other.id);
9830
10699
  suppressIds.forEach((id) => this.silencedThisSession.add(id));
@@ -9838,7 +10707,12 @@ class IntentRuleEvaluator {
9838
10707
  }
9839
10708
  return { kind: "none" };
9840
10709
  }
9841
- }
10710
+ };
10711
+ _IntentRuleEvaluator.EXIT_SIGNALS = [
10712
+ "exit_intent",
10713
+ "back_button"
10714
+ ];
10715
+ let IntentRuleEvaluator = _IntentRuleEvaluator;
9842
10716
  class ContactScoresFetcher {
9843
10717
  constructor(cfg) {
9844
10718
  this.lastFetchedAt = 0;
@@ -10192,7 +11066,7 @@ class PrefetchBundleClient {
10192
11066
  }
10193
11067
  let response;
10194
11068
  try {
10195
- response = await fetch(url, { method: "GET", headers });
11069
+ response = await fetch(url, { method: "GET", headers, cache: "no-cache" });
10196
11070
  } catch (err) {
10197
11071
  logger.warn("prefetch-bundle fetch network error:", err);
10198
11072
  return this.currentBundle;
@@ -11217,8 +12091,8 @@ class AegisWidgetManager {
11217
12091
  { label: "Prize 3", color: "#48dbfb" },
11218
12092
  { label: "Prize 4", color: "#ff6348" }
11219
12093
  ];
11220
- const SVG_NS = "http://www.w3.org/2000/svg";
11221
- const wheel = document.createElementNS(SVG_NS, "svg");
12094
+ const SVG_NS2 = "http://www.w3.org/2000/svg";
12095
+ const wheel = document.createElementNS(SVG_NS2, "svg");
11222
12096
  wheel.setAttribute("viewBox", "-160 -160 320 320");
11223
12097
  wheel.setAttribute("width", "300");
11224
12098
  wheel.setAttribute("height", "300");
@@ -11234,7 +12108,7 @@ class AegisWidgetManager {
11234
12108
  const x2 = Math.cos(end) * radius;
11235
12109
  const y2 = Math.sin(end) * radius;
11236
12110
  const largeArc = anglePer > Math.PI ? 1 : 0;
11237
- const path = document.createElementNS(SVG_NS, "path");
12111
+ const path = document.createElementNS(SVG_NS2, "path");
11238
12112
  path.setAttribute(
11239
12113
  "d",
11240
12114
  `M 0 0 L ${x1.toFixed(2)} ${y1.toFixed(2)} A ${radius} ${radius} 0 ${largeArc} 1 ${x2.toFixed(2)} ${y2.toFixed(2)} Z`
@@ -11246,7 +12120,7 @@ class AegisWidgetManager {
11246
12120
  const labelAngle = start + anglePer / 2;
11247
12121
  const lx = Math.cos(labelAngle) * radius * 0.65;
11248
12122
  const ly = Math.sin(labelAngle) * radius * 0.65;
11249
- const text = document.createElementNS(SVG_NS, "text");
12123
+ const text = document.createElementNS(SVG_NS2, "text");
11250
12124
  text.setAttribute("x", lx.toFixed(2));
11251
12125
  text.setAttribute("y", ly.toFixed(2));
11252
12126
  text.setAttribute("fill", "#ffffff");
@@ -11358,7 +12232,8 @@ class AegisWidgetManager {
11358
12232
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
11359
12233
  `;
11360
12234
  const closeBtn = document.createElement("button");
11361
- closeBtn.innerHTML = "";
12235
+ closeBtn.innerHTML = lucideSvg("x", { size: 16 });
12236
+ closeBtn.setAttribute("aria-label", "Close");
11362
12237
  closeBtn.className = "aegis-spin-wheel-close";
11363
12238
  closeBtn.style.cssText = `
11364
12239
  position: absolute;
@@ -12338,8 +13213,9 @@ class AegisWidgetManager {
12338
13213
  }
12339
13214
  if (config.show_timer && config.timer_minutes) {
12340
13215
  const timer = document.createElement("div");
12341
- timer.style.cssText = "margin: 0 0 24px 0; text-align: center; font-size: 14px; color: #999;";
12342
- timer.textContent = `⏰ Offer expires in ${config.timer_minutes} minutes`;
13216
+ timer.style.cssText = "margin: 0 0 24px 0; display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 14px; color: #999;";
13217
+ timer.innerHTML = lucideSvg("clock", { size: 14 });
13218
+ timer.appendChild(document.createTextNode(`Offer expires in ${config.timer_minutes} minutes`));
12343
13219
  modal.appendChild(timer);
12344
13220
  }
12345
13221
  const ctaButton = document.createElement("button");
@@ -12470,7 +13346,7 @@ class AegisWidgetManager {
12470
13346
  });
12471
13347
  modal.innerHTML = `
12472
13348
  <div style="text-align: center; padding: 20px;">
12473
- <div style="font-size: 48px; margin-bottom: 16px;">✅</div>
13349
+ <div style="margin-bottom: 16px; color: #10b981;">${lucideSvg("check", { size: 48 })}</div>
12474
13350
  <h3 style="margin: 0 0 12px 0; color: #1a73e8;">Thank You!</h3>
12475
13351
  <p style="margin: 0; color: #666;">Check your email for your discount code!</p>
12476
13352
  </div>
@@ -12716,6 +13592,9 @@ class AegisMessageRuntime {
12716
13592
  getCartState: () => this.deriveCartState()
12717
13593
  });
12718
13594
  this.intentRuleEvaluator = new IntentRuleEvaluator();
13595
+ if (config.governance) {
13596
+ this.intentRuleEvaluator.setGovernance(config.governance);
13597
+ }
12719
13598
  this.contactScores = new ContactScoresFetcher({
12720
13599
  // Falls back to same-origin when no apiHost configured (storefront
12721
13600
  // typical case) — matches WidgetManager/InAppManager handling