@active-reach/web-sdk 1.21.0 → 1.25.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 (45) 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 +6 -0
  7. package/dist/chat/AegisChat.d.ts.map +1 -1
  8. package/dist/core/prefetch-bundle-client.d.ts.map +1 -1
  9. package/dist/inapp/AegisInAppManager.d.ts +32 -0
  10. package/dist/inapp/AegisInAppManager.d.ts.map +1 -1
  11. package/dist/inapp/lucide-svg.d.ts +46 -0
  12. package/dist/inapp/lucide-svg.d.ts.map +1 -0
  13. package/dist/inapp/renderers/active-web-chat.d.ts.map +1 -1
  14. package/dist/inapp/renderers/card-style.d.ts +28 -0
  15. package/dist/inapp/renderers/card-style.d.ts.map +1 -0
  16. package/dist/inapp/renderers/card-template.d.ts +127 -0
  17. package/dist/inapp/renderers/card-template.d.ts.map +1 -0
  18. package/dist/inapp/renderers/carousel-cards.d.ts.map +1 -1
  19. package/dist/inapp/renderers/games.d.ts.map +1 -1
  20. package/dist/inapp/renderers/hero.d.ts.map +1 -1
  21. package/dist/inapp/renderers/index.d.ts +3 -1
  22. package/dist/inapp/renderers/index.d.ts.map +1 -1
  23. package/dist/inapp/renderers/product-recommendation.d.ts +19 -5
  24. package/dist/inapp/renderers/product-recommendation.d.ts.map +1 -1
  25. package/dist/inapp/renderers/progress-bar.d.ts.map +1 -1
  26. package/dist/inapp/renderers/sticky-bar.d.ts.map +1 -1
  27. package/dist/inapp/renderers/stories.d.ts.map +1 -1
  28. package/dist/inapp/renderers/types.d.ts +10 -0
  29. package/dist/inapp/renderers/types.d.ts.map +1 -1
  30. package/dist/inapp/renderers/video.d.ts.map +1 -1
  31. package/dist/index.js +1460 -272
  32. package/dist/index.js.map +1 -1
  33. package/dist/push/AegisWebPush.d.ts +1 -6
  34. package/dist/push/AegisWebPush.d.ts.map +1 -1
  35. package/dist/push/AegisWebPush.js +20 -1
  36. package/dist/push/AegisWebPush.js.map +1 -1
  37. package/dist/react.js +1 -1
  38. package/dist/runtime/AegisMessageRuntime.d.ts +11 -1
  39. package/dist/runtime/AegisMessageRuntime.d.ts.map +1 -1
  40. package/dist/triggers/IntentRuleEvaluator.d.ts +21 -0
  41. package/dist/triggers/IntentRuleEvaluator.d.ts.map +1 -1
  42. package/dist/utils/consent.d.ts.map +1 -1
  43. package/dist/widgets/AegisWidgetManager.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. 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,921 @@ 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 buildCountdownElement(el, fg, muted) {
427
+ const targetStr = typeof el.countdown_target === "string" ? el.countdown_target : "";
428
+ const target = targetStr ? Date.parse(targetStr) : NaN;
429
+ if (!targetStr || isNaN(target)) return null;
430
+ const wrap = document.createElement("div");
431
+ wrap.style.cssText = "display:flex;align-items:baseline;gap:8px;font-variant-numeric:tabular-nums;margin:2px 0;";
432
+ const label = document.createElement("span");
433
+ label.textContent = typeof el.countdown_label === "string" && el.countdown_label || "Ends in";
434
+ label.style.cssText = `font-size:12px;color:${muted};`;
435
+ const time = document.createElement("span");
436
+ time.style.cssText = `font-size:15px;font-weight:700;color:${fg};letter-spacing:0.2px;`;
437
+ wrap.appendChild(label);
438
+ wrap.appendChild(time);
439
+ const pad = (n) => String(n).padStart(2, "0");
440
+ let started = false;
441
+ let timer = 0;
442
+ const tick = () => {
443
+ if (started && !wrap.isConnected) {
444
+ if (timer) clearInterval(timer);
445
+ return;
446
+ }
447
+ const ms = target - Date.now();
448
+ if (ms <= 0) {
449
+ time.textContent = "00:00:00";
450
+ if (timer) clearInterval(timer);
451
+ return;
452
+ }
453
+ const s = Math.floor(ms / 1e3);
454
+ const d = Math.floor(s / 86400);
455
+ const h = Math.floor(s % 86400 / 3600);
456
+ const m2 = Math.floor(s % 3600 / 60);
457
+ const ss = s % 60;
458
+ time.textContent = d > 0 ? `${d}d ${pad(h)}:${pad(m2)}:${pad(ss)}` : `${pad(h)}:${pad(m2)}:${pad(ss)}`;
459
+ started = true;
460
+ };
461
+ tick();
462
+ timer = setInterval(tick, 1e3);
463
+ return wrap;
464
+ }
465
+ function buildRatingInputElement(el, ctx, fg) {
466
+ const wrap = document.createElement("div");
467
+ wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
468
+ if (el.content) {
469
+ const q = document.createElement("div");
470
+ q.textContent = el.content;
471
+ q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
472
+ wrap.appendChild(q);
473
+ }
474
+ const scale = typeof el.rating_scale === "number" && el.rating_scale >= 2 && el.rating_scale <= 10 ? Math.floor(el.rating_scale) : 5;
475
+ const row = document.createElement("div");
476
+ row.style.cssText = "display:flex;gap:6px;";
477
+ const stars = [];
478
+ let submitted = false;
479
+ const paint = (upto) => {
480
+ stars.forEach((s, idx) => {
481
+ const on = idx < upto;
482
+ s.setAttribute("stroke", on ? "#f59e0b" : "#d1d5db");
483
+ s.setAttribute("fill", on ? "#f59e0b" : "none");
484
+ });
485
+ };
486
+ for (let i = 1; i <= scale; i++) {
487
+ const btn = document.createElement("button");
488
+ btn.type = "button";
489
+ btn.setAttribute("aria-label", `${i} star${i > 1 ? "s" : ""}`);
490
+ btn.style.cssText = "cursor:pointer;border:none;background:none;padding:0;line-height:0;";
491
+ const svg = lucideSvgEl("star", { size: 26, color: "#d1d5db" });
492
+ if (svg) {
493
+ btn.appendChild(svg);
494
+ stars.push(svg);
495
+ }
496
+ btn.addEventListener("mouseenter", () => {
497
+ if (!submitted) paint(i);
498
+ });
499
+ btn.addEventListener("mouseleave", () => {
500
+ if (!submitted) paint(0);
501
+ });
502
+ btn.addEventListener("click", () => {
503
+ var _a, _b;
504
+ if (submitted) return;
505
+ submitted = true;
506
+ paint(i);
507
+ ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "rating_input", stars: i } });
508
+ void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "rating", { stars: i }));
509
+ (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, i >= Math.ceil(scale * 0.6) ? "affirm" : "acknowledge");
510
+ appendThanks(wrap, el, fg, "Thanks for rating us!");
511
+ });
512
+ row.appendChild(btn);
513
+ }
514
+ wrap.appendChild(row);
515
+ return wrap;
516
+ }
517
+ function appendThanks(wrap, el, fg, fallback) {
518
+ const msg = typeof el.thanks_text === "string" && el.thanks_text.trim() ? el.thanks_text : fallback;
519
+ if (!msg) return;
520
+ const note = document.createElement("div");
521
+ note.textContent = msg;
522
+ note.setAttribute("role", "status");
523
+ note.style.cssText = `font-size:12px;font-weight:600;color:${fg};opacity:0.85;margin-top:4px;`;
524
+ wrap.appendChild(note);
525
+ }
526
+ function buildPollElement(el, ctx, fg, accent) {
527
+ const opts = Array.isArray(el.poll_options) ? el.poll_options.filter((o) => typeof o === "string" && o.trim().length > 0) : [];
528
+ if (opts.length < 2) return null;
529
+ const wrap = document.createElement("div");
530
+ wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
531
+ if (el.content) {
532
+ const q = document.createElement("div");
533
+ q.textContent = el.content;
534
+ q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
535
+ wrap.appendChild(q);
536
+ }
537
+ let submitted = false;
538
+ opts.forEach((label, idx) => {
539
+ const btn = document.createElement("button");
540
+ btn.type = "button";
541
+ btn.textContent = label;
542
+ btn.style.cssText = `text-align:left;cursor:pointer;border:1px solid color-mix(in srgb, ${accent} 35%, transparent);background:transparent;color:${fg};border-radius:10px;padding:9px 12px;font-size:13px;font-weight:600;transition:background .12s,border-color .12s;`;
543
+ btn.addEventListener("mouseenter", () => {
544
+ if (!submitted) btn.style.background = `color-mix(in srgb, ${accent} 8%, transparent)`;
545
+ });
546
+ btn.addEventListener("mouseleave", () => {
547
+ if (!submitted) btn.style.background = "transparent";
548
+ });
549
+ btn.addEventListener("click", () => {
550
+ var _a, _b;
551
+ if (submitted) return;
552
+ submitted = true;
553
+ Array.from(wrap.querySelectorAll("button")).forEach((b) => {
554
+ b.disabled = true;
555
+ b.style.opacity = "0.6";
556
+ });
557
+ btn.style.background = accent;
558
+ btn.style.color = "#fff";
559
+ btn.style.opacity = "1";
560
+ ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "poll", option_index: idx, option_label: label } });
561
+ void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "poll", { option_index: idx, option_label: label }));
562
+ (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, "affirm");
563
+ appendThanks(wrap, el, fg, "Thanks for your answer!");
564
+ });
565
+ wrap.appendChild(btn);
566
+ });
567
+ return wrap;
568
+ }
569
+ function buildNpsElement(el, ctx, fg, accent, muted) {
570
+ const wrap = document.createElement("div");
571
+ wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
572
+ if (el.content) {
573
+ const q = document.createElement("div");
574
+ q.textContent = el.content;
575
+ q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
576
+ wrap.appendChild(q);
577
+ }
578
+ const row = document.createElement("div");
579
+ row.style.cssText = "display:flex;flex-wrap:wrap;gap:4px;";
580
+ let submitted = false;
581
+ for (let n = 0; n <= 10; n++) {
582
+ const btn = document.createElement("button");
583
+ btn.type = "button";
584
+ btn.textContent = String(n);
585
+ btn.setAttribute("aria-label", `Score ${n}`);
586
+ btn.style.cssText = `cursor:pointer;min-width:26px;height:30px;border:1px solid color-mix(in srgb, ${accent} 30%, transparent);background:transparent;color:${fg};border-radius:7px;font-size:12px;font-weight:700;transition:background .12s;`;
587
+ btn.addEventListener("mouseenter", () => {
588
+ if (!submitted) btn.style.background = `color-mix(in srgb, ${accent} 10%, transparent)`;
589
+ });
590
+ btn.addEventListener("mouseleave", () => {
591
+ if (!submitted) btn.style.background = "transparent";
592
+ });
593
+ btn.addEventListener("click", () => {
594
+ var _a, _b;
595
+ if (submitted) return;
596
+ submitted = true;
597
+ Array.from(row.children).forEach((b) => {
598
+ b.disabled = true;
599
+ b.style.opacity = "0.55";
600
+ });
601
+ btn.style.background = accent;
602
+ btn.style.color = "#fff";
603
+ btn.style.opacity = "1";
604
+ ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "nps", score: n } });
605
+ void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "nps", { score: n }));
606
+ (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, n >= 9 ? "affirm" : n >= 7 ? "acknowledge" : "empathize");
607
+ appendThanks(wrap, el, fg, "Thanks for your feedback!");
608
+ });
609
+ row.appendChild(btn);
610
+ }
611
+ wrap.appendChild(row);
612
+ const scale = document.createElement("div");
613
+ scale.style.cssText = `display:flex;justify-content:space-between;font-size:10px;color:${muted};`;
614
+ const lo = document.createElement("span");
615
+ lo.textContent = "Not likely";
616
+ const hi = document.createElement("span");
617
+ hi.textContent = "Very likely";
618
+ scale.appendChild(lo);
619
+ scale.appendChild(hi);
620
+ wrap.appendChild(scale);
621
+ return wrap;
622
+ }
623
+ function buildProgressElement(el, ctx, accent, muted) {
624
+ const { trackEvent, campaign, sanitizeColor } = ctx;
625
+ const goal = el.goal_type || "cart_total";
626
+ const moneyGoal = goal === "cart_total";
627
+ const fill = el.fill ? sanitizeColor(el.fill) : accent;
628
+ const props = () => ctx.properties && ctx.properties() || {};
629
+ const sym = () => {
630
+ var _a;
631
+ if (el.currency_symbol) return el.currency_symbol;
632
+ const code = ctx.getCartState && ((_a = ctx.getCartState()) == null ? void 0 : _a.currency);
633
+ const M = { INR: "₹", USD: "$", EUR: "€", GBP: "£" };
634
+ return code && M[code] || "";
635
+ };
636
+ const threshold = () => {
637
+ if (typeof el.threshold === "number" && el.threshold > 0) return el.threshold;
638
+ if (el.threshold_source === "free_delivery_setting") {
639
+ const v = Number(props()["cart.free_delivery_above"]);
640
+ if (v > 0) return v;
641
+ }
642
+ return 0;
643
+ };
644
+ const readCur = () => {
645
+ const cart = ctx.getCartState && ctx.getCartState();
646
+ if (goal === "items_in_cart") return Number(cart == null ? void 0 : cart.itemCount) || 0;
647
+ if (goal === "cart_total") return Number(cart == null ? void 0 : cart.total) || 0;
648
+ const p = props();
649
+ if (goal === "loyalty_points") return Number(p["loyalty.points"]) || 0;
650
+ if (goal === "referrals") return Number(p["referrals.count"]) || 0;
651
+ return 0;
652
+ };
653
+ const wrap = document.createElement("div");
654
+ wrap.style.cssText = "display:flex;flex-direction:column;gap:3px;min-width:0;width:100%;";
655
+ const sub = document.createElement("div");
656
+ sub.style.cssText = `font-size:11.5px;line-height:1.2;color:${muted};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`;
657
+ const shell = document.createElement("div");
658
+ shell.style.cssText = `height:3px;border-radius:999px;background:color-mix(in srgb, ${fill} 16%, transparent);overflow:hidden;`;
659
+ const bar = document.createElement("div");
660
+ bar.style.cssText = `height:100%;border-radius:999px;background:${fill};width:0%;transition:width .4s cubic-bezier(.4,0,.2,1);`;
661
+ shell.appendChild(bar);
662
+ wrap.appendChild(sub);
663
+ wrap.appendChild(shell);
664
+ let firedUnlock = false;
665
+ const update = () => {
666
+ var _a;
667
+ const th = threshold();
668
+ const cur = readCur();
669
+ const pct = th > 0 ? Math.max(0, Math.min(100, cur / th * 100)) : 0;
670
+ bar.style.width = `${pct}%`;
671
+ if (th > 0 && pct >= 100) {
672
+ sub.textContent = el.reward_text || "Unlocked!";
673
+ sub.style.color = "#10b981";
674
+ sub.style.fontWeight = "700";
675
+ if (!firedUnlock) {
676
+ firedUnlock = true;
677
+ trackEvent(campaign.id, "clicked", { stepId: el.id || "unlocked" });
678
+ if (el.celebrate_on_complete !== false) (_a = ctx.playReaction) == null ? void 0 : _a.call(ctx, wrap, "celebrate");
679
+ }
680
+ } else {
681
+ sub.style.color = muted;
682
+ sub.style.fontWeight = "";
683
+ firedUnlock = false;
684
+ const remaining = Math.max(0, th - cur);
685
+ const pre = moneyGoal ? sym() : "";
686
+ const pctStr = el.show_pct && th > 0 ? ` · ${Math.round(pct)}%` : "";
687
+ sub.textContent = th > 0 ? `Shop for ${pre}${remaining.toFixed(0)} more${pctStr}` : el.reward_text || "";
688
+ }
689
+ };
690
+ update();
691
+ const timer = setInterval(update, 1e3);
692
+ if (typeof window !== "undefined") {
693
+ window.addEventListener("beforeunload", () => clearInterval(timer), { once: true });
694
+ }
695
+ return wrap;
696
+ }
697
+ function renderCardFromTemplate(card, opts) {
698
+ const { ctx, action, layout, layoutAccent } = opts;
699
+ const inline = action === "cart";
700
+ const { campaign, trackEvent, sanitizeUrl, sanitizeColor } = ctx;
701
+ const tpl = applyPerCardOverride(opts.template, asLook(card.style));
702
+ const frame = tpl.frame || {};
703
+ const meta = card.metadata || {};
704
+ const accent = typeof frame.accent === "string" && frame.accent ? sanitizeColor(frame.accent) : layoutAccent;
705
+ const brand = `var(--aegis-brand-accent, ${accent})`;
706
+ const fg = contrastFg(frame.bg ? sanitizeColor(frame.bg) : "#ffffff");
707
+ const muted = fg === "#ffffff" ? "rgba(255,255,255,0.72)" : "#6b7280";
708
+ const tile = document.createElement("div");
709
+ const radius = frame.radius !== void 0 ? `${parseInt(String(frame.radius), 10) || 12}px` : "12px";
710
+ const fLayout = frame.layout;
711
+ const dir = fLayout === "row" ? "row" : "column";
712
+ const tileGap = frame.gap != null ? frame.gap : 6;
713
+ 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;";
714
+ tile.style.cssText = `
715
+ box-sizing:border-box; display:flex; flex-direction:${dir}; ${fLayout === "row" ? "align-items:center;" : ""} gap:${tileGap}px; position:relative;
716
+ color:${fg};
717
+ background:${frame.bg ? sanitizeColor(frame.bg) : "#ffffff"};
718
+ border:1px solid ${frame.border ? sanitizeColor(frame.border) : "transparent"};
719
+ border-radius:${radius}; padding:8px;
720
+ ${frame.max_width ? `max-width:${frame.max_width}px;` : ""}${frame.min_width ? `min-width:${frame.min_width}px;` : ""}
721
+ ${frame.height ? `height:${frame.height}px;` : ""}${frame.min_height ? `min-height:${frame.min_height}px;` : ""}
722
+ ${sizing}
723
+ cursor:${card.cta_url || inline ? "pointer" : "default"}; transition:transform 0.15s ease;
724
+ `;
725
+ const elements = tpl.elements && tpl.elements.length ? tpl.elements : DEFAULT_CARD_TEMPLATE.elements;
726
+ let imageWrap = null;
727
+ const paintInto = (container, els) => {
728
+ for (const el of els) {
729
+ switch (el.type) {
730
+ case "image": {
731
+ const wrap = document.createElement("div");
732
+ 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;`;
733
+ const safeVideo = card.video_url ? sanitizeUrl(card.video_url) : "";
734
+ if (safeVideo) {
735
+ const v = document.createElement("video");
736
+ v.src = safeVideo;
737
+ v.muted = true;
738
+ v.loop = true;
739
+ v.autoplay = true;
740
+ v.playsInline = true;
741
+ v.setAttribute("playsinline", "");
742
+ v.style.cssText = "width:100%;height:100%;object-fit:cover;display:block;";
743
+ wrap.appendChild(v);
744
+ } else if (card.image_url) {
745
+ const safe = sanitizeUrl(card.image_url);
746
+ if (safe) {
747
+ const img = document.createElement("img");
748
+ img.src = safe;
749
+ img.alt = "";
750
+ img.loading = "lazy";
751
+ img.style.cssText = "width:100%;height:100%;object-fit:cover;display:block;";
752
+ wrap.appendChild(img);
753
+ }
754
+ }
755
+ imageWrap = wrap;
756
+ container.appendChild(wrap);
757
+ break;
758
+ }
759
+ case "badge": {
760
+ const text = el.text;
761
+ if (!text) break;
762
+ const badge = document.createElement("span");
763
+ badge.textContent = text;
764
+ const bg = el.color ? sanitizeColor(el.color) : brand;
765
+ 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;`;
766
+ (imageWrap || container).appendChild(badge);
767
+ break;
768
+ }
769
+ case "price": {
770
+ const node = paintPrice(card, el, accent, ctx);
771
+ if (node) container.appendChild(node);
772
+ break;
773
+ }
774
+ case "title": {
775
+ if (!card.title) break;
776
+ const t = document.createElement("div");
777
+ t.textContent = card.title;
778
+ const lines = el.lines && el.lines > 0 ? el.lines : 2;
779
+ 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;`;
780
+ container.appendChild(t);
781
+ break;
782
+ }
783
+ case "subtitle": {
784
+ if (!card.body) break;
785
+ const s = document.createElement("div");
786
+ s.textContent = card.body;
787
+ const lines = el.lines && el.lines > 0 ? el.lines : 2;
788
+ 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;`;
789
+ container.appendChild(s);
790
+ break;
791
+ }
792
+ case "rating": {
793
+ const node = paintRating(card);
794
+ if (node) container.appendChild(node);
795
+ break;
796
+ }
797
+ case "category": {
798
+ const cat = meta.category;
799
+ if (typeof cat !== "string" || !cat) break;
800
+ const chip = document.createElement("span");
801
+ chip.textContent = cat;
802
+ 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;";
803
+ container.appendChild(chip);
804
+ break;
805
+ }
806
+ // ── rich-card-editor elements (P0) ──────────────────────────────────
807
+ case "icon": {
808
+ const px = ICON_PX[el.size || "md"] || 30;
809
+ const node = document.createElement("div");
810
+ const iconColor = el.color ? sanitizeColor(el.color) : fg;
811
+ if (el.bubble) {
812
+ const bubbleTint = el.color ? sanitizeColor(el.color) : brand;
813
+ 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);`;
814
+ } else {
815
+ node.style.cssText = "flex:0 0 auto;display:flex;align-items:center;";
816
+ }
817
+ const svg = lucideSvgEl(el.icon_name || "circle-check", {
818
+ size: el.bubble ? Math.round(px * 0.55) : Math.round(px * 0.7),
819
+ color: iconColor,
820
+ strokeWidth: 2
821
+ });
822
+ if (svg) node.appendChild(svg);
823
+ container.appendChild(node);
824
+ break;
825
+ }
826
+ case "text": {
827
+ const t = document.createElement("div");
828
+ const fs = TEXT_PX[el.size || "md"] || 13;
829
+ 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;`;
830
+ t.textContent = interpolateTokens(el.content ?? "", ctx);
831
+ container.appendChild(t);
832
+ break;
833
+ }
834
+ case "spacer": {
835
+ const sp = document.createElement("div");
836
+ sp.style.height = `${el.height ?? 8}px`;
837
+ container.appendChild(sp);
838
+ break;
839
+ }
840
+ case "progress": {
841
+ container.appendChild(buildProgressElement(el, ctx, brand, muted));
842
+ break;
843
+ }
844
+ case "countdown": {
845
+ const cd = buildCountdownElement(el, fg, muted);
846
+ if (cd) container.appendChild(cd);
847
+ break;
848
+ }
849
+ case "rating_input": {
850
+ container.appendChild(buildRatingInputElement(el, ctx, fg));
851
+ break;
852
+ }
853
+ case "poll": {
854
+ const p = buildPollElement(el, ctx, fg, brand);
855
+ if (p) container.appendChild(p);
856
+ break;
857
+ }
858
+ case "nps": {
859
+ container.appendChild(buildNpsElement(el, ctx, fg, brand, muted));
860
+ break;
861
+ }
862
+ case "group": {
863
+ const g = document.createElement("div");
864
+ const gdir = el.layout === "row" ? "row" : "column";
865
+ 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;"}`;
866
+ paintInto(g, el.elements || []);
867
+ container.appendChild(g);
868
+ break;
869
+ }
870
+ case "cta": {
871
+ const label = el.text || card.cta_text || (inline ? "Add" : "Shop");
872
+ const cta = document.createElement("button");
873
+ cta.className = "aegis-cta";
874
+ cta.textContent = label;
875
+ const overlayOnImage = !!el.overlay && !!imageWrap;
876
+ if (overlayOnImage) {
877
+ 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);`;
878
+ } else {
879
+ 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;`;
880
+ }
881
+ cta.addEventListener("click", (e) => {
882
+ e.stopPropagation();
883
+ trackEvent(campaign.id, "clicked", { stepId: el.id || "add" });
884
+ if (inline) {
885
+ if (typeof window !== "undefined") {
886
+ window.dispatchEvent(
887
+ new CustomEvent("aegis:add-to-cart", {
888
+ detail: {
889
+ campaign_id: campaign.id,
890
+ product_id: meta.product_id,
891
+ sku: meta.sku,
892
+ title: card.title,
893
+ price: meta.price ?? card.body,
894
+ image_url: card.image_url,
895
+ cta_url: card.cta_url
896
+ }
897
+ })
898
+ );
899
+ }
900
+ return;
901
+ }
902
+ if (card.cta_url) {
903
+ const safe = sanitizeUrl(card.cta_url);
904
+ if (safe) ctx.navigate ? ctx.navigate(safe) : window.location.href = safe;
905
+ }
906
+ });
907
+ (overlayOnImage ? imageWrap : container).appendChild(cta);
908
+ break;
909
+ }
910
+ }
911
+ }
912
+ };
913
+ paintInto(tile, elements);
914
+ const open = (e) => {
915
+ e.stopPropagation();
916
+ trackEvent(campaign.id, "clicked");
917
+ if (inline) {
918
+ if (typeof window !== "undefined") {
919
+ window.dispatchEvent(
920
+ new CustomEvent("aegis:open-product", {
921
+ detail: {
922
+ campaign_id: campaign.id,
923
+ product_id: meta.product_id,
924
+ sku: meta.sku,
925
+ cta_url: card.cta_url
926
+ }
927
+ })
928
+ );
929
+ }
930
+ return;
931
+ }
932
+ if (card.cta_url) {
933
+ const safe = sanitizeUrl(card.cta_url);
934
+ if (safe) ctx.navigate ? ctx.navigate(safe) : window.location.href = safe;
935
+ }
936
+ };
937
+ if (inline || card.cta_url) tile.addEventListener("click", open);
938
+ return tile;
939
+ }
28
940
  const MAX_CARDS_RENDERED = 10;
29
941
  function renderCarouselCards(ctx) {
30
- const { campaign, trackEvent, sanitizeUrl, sanitizeColor, log, addAnimationStyles } = ctx;
942
+ const { campaign, trackEvent, sanitizeColor, log, addAnimationStyles } = ctx;
31
943
  const ic = campaign.interactive_config || {};
32
944
  const rawCards = Array.isArray(ic.cards) ? ic.cards : [];
33
945
  const cards = rawCards.slice(0, MAX_CARDS_RENDERED);
@@ -70,7 +982,7 @@ function renderCarouselCards(ctx) {
70
982
  headerText.appendChild(body);
71
983
  header.appendChild(headerText);
72
984
  const closeBtn = document.createElement("button");
73
- closeBtn.textContent = "✕";
985
+ closeBtn.innerHTML = lucideSvg("x", { size: 16 });
74
986
  closeBtn.setAttribute("aria-label", "Close");
75
987
  closeBtn.style.cssText = `
76
988
  background: transparent; border: none; color: inherit;
@@ -89,68 +1001,16 @@ function renderCarouselCards(ctx) {
89
1001
  padding-bottom: 4px; -webkit-overflow-scrolling: touch;
90
1002
  `;
91
1003
  track.style.msOverflowStyle = "none";
1004
+ const template = resolveGlobalTemplate(ic);
92
1005
  cards.forEach((c, i) => {
93
- const tile = document.createElement("div");
1006
+ const tile = renderCardFromTemplate(c, {
1007
+ template,
1008
+ ctx,
1009
+ action: "navigate",
1010
+ layout: "row",
1011
+ layoutAccent: accent
1012
+ });
94
1013
  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
1014
  track.appendChild(tile);
155
1015
  });
156
1016
  card.appendChild(track);
@@ -314,7 +1174,7 @@ function renderStickyBar(ctx) {
314
1174
  };
315
1175
  if (dismissible) {
316
1176
  const close = document.createElement("button");
317
- close.textContent = "✕";
1177
+ close.innerHTML = lucideSvg("x", { size: 16 });
318
1178
  close.setAttribute("aria-label", "Dismiss");
319
1179
  close.style.cssText = `
320
1180
  background: transparent; border: none; color: inherit;
@@ -344,13 +1204,12 @@ function buildProgressBar(ctx, inline) {
344
1204
  const source = ic.progress_source === "sse" ? "sse" : "client";
345
1205
  addAnimationStyles();
346
1206
  const brand = sanitizeColor(campaign.background_color || "#4169e1");
1207
+ const accentVar = `var(--aegis-brand-accent, ${brand})`;
347
1208
  const branded = ic.surface_style === "branded";
348
- const bg = branded ? brand : "#ffffff";
1209
+ const bg = branded ? accentVar : "#ffffff";
349
1210
  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`;
1211
+ const fill = ic.progress_fill_color ? sanitizeColor(ic.progress_fill_color) : branded ? "#ffffff" : accentVar;
1212
+ const track = branded ? "rgba(255,255,255,0.25)" : `color-mix(in srgb, ${fill} 14%, transparent)`;
354
1213
  const bar = document.createElement("div");
355
1214
  bar.className = "aegis-in-app-progress-bar";
356
1215
  bar.setAttribute("data-campaign-id", campaign.id);
@@ -730,7 +1589,7 @@ function renderCoachmarkTour(ctx) {
730
1589
  }
731
1590
  const MAX_PRODUCTS = 24;
732
1591
  function renderProductRecommendation(ctx) {
733
- const { campaign, trackEvent, sanitizeUrl, sanitizeColor, log, addAnimationStyles } = ctx;
1592
+ const { campaign, trackEvent, sanitizeColor, log, addAnimationStyles } = ctx;
734
1593
  const ic = campaign.interactive_config || {};
735
1594
  const rawCards = Array.isArray(ic.cards) ? ic.cards : [];
736
1595
  const cards = rawCards.slice(0, MAX_PRODUCTS);
@@ -739,7 +1598,7 @@ function renderProductRecommendation(ctx) {
739
1598
  return;
740
1599
  }
741
1600
  const layout = ic.rec_layout || "grid";
742
- const ctaDefault = ic.rec_cta_text || "Shop now";
1601
+ const template = resolveGlobalTemplate(ic);
743
1602
  addAnimationStyles();
744
1603
  const bg = "#ffffff";
745
1604
  const fg = "#0f172a";
@@ -782,7 +1641,7 @@ function renderProductRecommendation(ctx) {
782
1641
  headerText.appendChild(body);
783
1642
  header.appendChild(headerText);
784
1643
  const close = document.createElement("button");
785
- close.textContent = "✕";
1644
+ close.innerHTML = lucideSvg("x", { size: 18 });
786
1645
  close.setAttribute("aria-label", "Close");
787
1646
  close.style.cssText = `
788
1647
  background: transparent; border: none; color: inherit;
@@ -807,82 +1666,9 @@ function renderProductRecommendation(ctx) {
807
1666
  `;
808
1667
  }
809
1668
  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);
1669
+ grid.appendChild(
1670
+ renderCardFromTemplate(c, { template, ctx, action: "navigate", layout, layoutAccent: accent })
1671
+ );
886
1672
  });
887
1673
  sheet.appendChild(grid);
888
1674
  overlay.appendChild(sheet);
@@ -894,6 +1680,88 @@ function renderProductRecommendation(ctx) {
894
1680
  });
895
1681
  document.body.appendChild(overlay);
896
1682
  }
1683
+ function renderProductRecommendationInline(ctx, target) {
1684
+ const { campaign, sanitizeColor, addAnimationStyles, log } = ctx;
1685
+ const ic = campaign.interactive_config || {};
1686
+ const rawCards = Array.isArray(ic.cards) ? ic.cards : [];
1687
+ const cards = rawCards.slice(0, MAX_PRODUCTS);
1688
+ if (cards.length === 0) {
1689
+ log("product_recommendation (inline) rendered with zero products — skipping", "warn");
1690
+ return false;
1691
+ }
1692
+ const layout = ic.rec_layout || "row";
1693
+ const template = resolveGlobalTemplate(ic);
1694
+ const fg = "#0f172a";
1695
+ const accent = sanitizeColor(campaign.background_color || "#4169e1");
1696
+ addAnimationStyles();
1697
+ const section = document.createElement("section");
1698
+ section.className = "aegis-in-app-product-rec-inline";
1699
+ section.setAttribute("data-campaign-id", campaign.id);
1700
+ section.style.cssText = `
1701
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1702
+ color: ${fg};
1703
+ `;
1704
+ if (campaign.title) {
1705
+ const title = document.createElement("h2");
1706
+ title.textContent = campaign.title;
1707
+ title.style.cssText = "font-weight: 700; font-size: 16px; margin: 0 0 2px;";
1708
+ section.appendChild(title);
1709
+ }
1710
+ if (campaign.body) {
1711
+ const body = document.createElement("div");
1712
+ body.textContent = campaign.body;
1713
+ body.style.cssText = "font-size: 13px; opacity: 0.65; line-height: 1.4; margin-bottom: 10px;";
1714
+ section.appendChild(body);
1715
+ }
1716
+ const grid = document.createElement("div");
1717
+ const isRow = layout !== "grid";
1718
+ if (layout === "grid") {
1719
+ grid.style.cssText = "display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;";
1720
+ } else {
1721
+ grid.style.cssText = `
1722
+ display: flex; gap: 10px; overflow-x: auto;
1723
+ scroll-snap-type: x proximity; padding: 0 0 6px;
1724
+ -webkit-overflow-scrolling: touch; scrollbar-width: none;
1725
+ `;
1726
+ grid.style.msOverflowStyle = "none";
1727
+ }
1728
+ cards.forEach((c) => {
1729
+ grid.appendChild(
1730
+ renderCardFromTemplate(c, { template, ctx, action: "cart", layout, layoutAccent: accent })
1731
+ );
1732
+ });
1733
+ if (isRow && cards.length > 2) {
1734
+ const wrap = document.createElement("div");
1735
+ wrap.style.cssText = "position: relative;";
1736
+ wrap.appendChild(grid);
1737
+ const arrow = document.createElement("button");
1738
+ arrow.setAttribute("aria-label", "Scroll for more");
1739
+ arrow.innerHTML = "›";
1740
+ arrow.style.cssText = `
1741
+ position: absolute; top: 50%; right: 0; transform: translateY(-50%);
1742
+ width: 30px; height: 30px; border-radius: 999px; border: 1px solid ${fg}1a;
1743
+ background: #fff; color: ${fg}; font-size: 18px; line-height: 1; cursor: pointer;
1744
+ box-shadow: 0 2px 8px rgba(0,0,0,0.12); display: flex; align-items: center;
1745
+ justify-content: center; z-index: 2;
1746
+ `;
1747
+ arrow.addEventListener("click", (e) => {
1748
+ e.stopPropagation();
1749
+ grid.scrollBy({ left: Math.round(grid.clientWidth * 0.8), behavior: "smooth" });
1750
+ });
1751
+ const syncArrow = () => {
1752
+ const atEnd = grid.scrollLeft + grid.clientWidth >= grid.scrollWidth - 8;
1753
+ arrow.style.display = atEnd ? "none" : "flex";
1754
+ };
1755
+ grid.addEventListener("scroll", syncArrow, { passive: true });
1756
+ setTimeout(syncArrow, 0);
1757
+ wrap.appendChild(arrow);
1758
+ section.appendChild(wrap);
1759
+ } else {
1760
+ section.appendChild(grid);
1761
+ }
1762
+ target.appendChild(section);
1763
+ return true;
1764
+ }
897
1765
  const MAX_GROUPS = 12;
898
1766
  const MAX_FRAMES = 20;
899
1767
  const DEFAULT_DURATION_MS = 5e3;
@@ -997,7 +1865,7 @@ function renderLauncherBar(ctx, cfg, target) {
997
1865
  if (floating) {
998
1866
  const dismiss = document.createElement("button");
999
1867
  dismiss.type = "button";
1000
- dismiss.textContent = "✕";
1868
+ dismiss.innerHTML = lucideSvg("x", { size: 14 });
1001
1869
  dismiss.setAttribute("aria-label", "Dismiss stories");
1002
1870
  dismiss.style.cssText = `
1003
1871
  flex: 0 0 auto; align-self: center; margin-left: auto;
@@ -1141,7 +2009,7 @@ function openViewer(ctx, cfg, startGroupIndex, startFrameIndex = 0) {
1141
2009
  };
1142
2010
  const close = document.createElement("button");
1143
2011
  close.type = "button";
1144
- close.textContent = "✕";
2012
+ close.innerHTML = lucideSvg("x", { size: 18 });
1145
2013
  close.setAttribute("aria-label", "Close");
1146
2014
  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
2015
  stage.appendChild(close);
@@ -1418,7 +2286,7 @@ function openGameModal(ctx, className, maxWidth = 360) {
1418
2286
  };
1419
2287
  const close = document.createElement("button");
1420
2288
  close.type = "button";
1421
- close.textContent = "✕";
2289
+ close.innerHTML = lucideSvg("x", { size: 15 });
1422
2290
  close.setAttribute("aria-label", "Close");
1423
2291
  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
2292
  close.addEventListener("click", () => dismiss(true));
@@ -2136,14 +3004,15 @@ function buildVideo(ctx, aspect, autoplay, loop) {
2136
3004
  });
2137
3005
  const pill = document.createElement("button");
2138
3006
  pill.type = "button";
2139
- pill.textContent = "🔇";
3007
+ pill.innerHTML = lucideSvg("volume-x", { size: 14 });
2140
3008
  pill.setAttribute("aria-label", "Unmute");
2141
3009
  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
3010
  pill.addEventListener("click", (e) => {
2143
3011
  var _a2;
2144
3012
  e.stopPropagation();
2145
3013
  vid.muted = !vid.muted;
2146
- pill.textContent = vid.muted ? "🔇" : "🔊";
3014
+ pill.innerHTML = lucideSvg(vid.muted ? "volume-x" : "volume-2", { size: 14 });
3015
+ pill.setAttribute("aria-label", vid.muted ? "Unmute" : "Mute");
2147
3016
  if (!vid.muted) void ((_a2 = vid.play) == null ? void 0 : _a2.call(vid).catch(() => {
2148
3017
  }));
2149
3018
  });
@@ -2231,7 +3100,7 @@ function renderVideo(ctx) {
2231
3100
  }
2232
3101
  const close = document.createElement("button");
2233
3102
  close.type = "button";
2234
- close.textContent = "✕";
3103
+ close.innerHTML = lucideSvg("x", { size: 15 });
2235
3104
  close.setAttribute("aria-label", "Close");
2236
3105
  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
3106
  close.addEventListener("click", () => {
@@ -2276,8 +3145,15 @@ const HEX = /^#[0-9a-fA-F]{3,8}$/;
2276
3145
  function safeColor(c, fallback) {
2277
3146
  return c && HEX.test(c) ? c : fallback;
2278
3147
  }
2279
- const RADIUS = { none: "0", lg: "12px", xl: "16px", "2xl": "20px" };
2280
3148
  const ASPECT = { "16:9": "16 / 9", "4:3": "4 / 3", "21:9": "21 / 9" };
3149
+ function _injectHeroResponsiveCss() {
3150
+ if (typeof document === "undefined") return;
3151
+ if (document.getElementById("aegis-hero-style")) return;
3152
+ const style = document.createElement("style");
3153
+ style.id = "aegis-hero-style";
3154
+ style.textContent = ".aegis-hero-root{border-radius:16px;}@media (min-width:768px){.aegis-hero-root{border-radius:0;min-height:45vh;max-height:70vh;}}";
3155
+ document.head.appendChild(style);
3156
+ }
2281
3157
  function cardHeadline(c) {
2282
3158
  return c.headline ?? c.title ?? "";
2283
3159
  }
@@ -2303,7 +3179,6 @@ function renderHeroInline(ctx, target) {
2303
3179
  chrome.accent && chrome.accent !== "brand" ? chrome.accent : campaign.background_color,
2304
3180
  "#4169e1"
2305
3181
  );
2306
- const radius = RADIUS[chrome.radius ?? "2xl"] ?? RADIUS["2xl"];
2307
3182
  const variant = chrome.variant ?? "hero_fullbleed";
2308
3183
  if (variant === "announcement_bar") {
2309
3184
  const c = cards[0];
@@ -2326,8 +3201,10 @@ function renderHeroInline(ctx, target) {
2326
3201
  trackEvent(campaign.id, "impression");
2327
3202
  return true;
2328
3203
  }
3204
+ _injectHeroResponsiveCss();
2329
3205
  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;"}`;
3206
+ root.className = "aegis-hero-root";
3207
+ 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
3208
  root.setAttribute("data-campaign-id", campaign.id);
2332
3209
  const track = document.createElement("div");
2333
3210
  track.style.cssText = "display:flex;height:100%;width:100%;transition:transform 0.5s ease;";
@@ -2459,6 +3336,7 @@ class AegisChat {
2459
3336
  this.title = config.title ?? "Shopping Assistant";
2460
3337
  this.greeting = config.greeting ?? "Hi! 👋 How can I help you today?";
2461
3338
  this.accent = config.accentColor ?? "#4169e1";
3339
+ this.surfaceStyle = config.surfaceStyle === "tinted" ? "tinted" : "solid";
2462
3340
  this.icon = config.icon ?? "sparkle";
2463
3341
  this.logoUrl = config.logoUrl;
2464
3342
  this.position = config.position ?? "bottom-right";
@@ -2729,6 +3607,10 @@ class AegisChat {
2729
3607
  const root = document.createElement("div");
2730
3608
  root.className = `aegis-chat-root aegis-chat-root--${this.position}`;
2731
3609
  root.style.setProperty("--aegis-chat-accent", this.accent);
3610
+ if (this.surfaceStyle === "tinted") {
3611
+ root.style.setProperty("--aegis-chat-fill", "color-mix(in srgb, var(--aegis-chat-accent,#4169e1) 16%, #ffffff)");
3612
+ root.style.setProperty("--aegis-chat-fill-fg", "#0f172a");
3613
+ }
2732
3614
  const bubble = document.createElement("button");
2733
3615
  bubble.className = "aegis-chat-bubble";
2734
3616
  bubble.setAttribute("aria-label", `Open ${this.title}`);
@@ -2750,7 +3632,7 @@ class AegisChat {
2750
3632
  const closeBtn = document.createElement("button");
2751
3633
  closeBtn.className = "aegis-chat-close";
2752
3634
  closeBtn.setAttribute("aria-label", "Close chat");
2753
- closeBtn.textContent = "✕";
3635
+ closeBtn.innerHTML = lucideSvg("x", { size: 16 });
2754
3636
  closeBtn.addEventListener("click", () => this.closePanel());
2755
3637
  header.appendChild(titleEl);
2756
3638
  header.appendChild(closeBtn);
@@ -2806,7 +3688,7 @@ class AegisChat {
2806
3688
  const tipClose = document.createElement("button");
2807
3689
  tipClose.className = "aegis-chat-tip-close";
2808
3690
  tipClose.setAttribute("aria-label", "Dismiss");
2809
- tipClose.textContent = "✕";
3691
+ tipClose.innerHTML = lucideSvg("x", { size: 14 });
2810
3692
  tipClose.addEventListener("click", (e) => {
2811
3693
  e.stopPropagation();
2812
3694
  this.dismissAttention();
@@ -3084,16 +3966,18 @@ const CHAT_CSS = `
3084
3966
  .aegis-chat-badge{position:absolute;top:-2px;right:-2px;min-width:20px;height:20px;padding:0 5px;border-radius:9999px;background:#ef4444;color:#fff;font-size:12px;font-weight:600;display:flex;align-items:center;justify-content:center}
3085
3967
  .aegis-chat-panel{position:absolute;bottom:0;right:0;width:380px;max-width:calc(100vw - 32px);height:560px;max-height:calc(100vh - 48px);background:#fff;border-radius:16px;box-shadow:0 16px 48px rgba(0,0,0,.24);display:none;flex-direction:column;overflow:hidden}
3086
3968
  .aegis-chat-panel--open{display:flex}
3087
- .aegis-chat-header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;background:var(--aegis-chat-accent,#4169e1);color:#fff}
3969
+ /* Header + customer bubbles use the "fill" surface — accent solid by default, or
3970
+ a soft tint when surfaceStyle:'tinted' sets --aegis-chat-fill/-fg on the root. */
3971
+ .aegis-chat-header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;background:var(--aegis-chat-fill,var(--aegis-chat-accent,#4169e1));color:var(--aegis-chat-fill-fg,#fff)}
3088
3972
  .aegis-chat-title{font-weight:600;font-size:15px}
3089
- .aegis-chat-close{background:transparent;border:none;color:#fff;font-size:16px;cursor:pointer;opacity:.85}
3973
+ .aegis-chat-close{background:transparent;border:none;color:var(--aegis-chat-fill-fg,#fff);font-size:16px;cursor:pointer;opacity:.85}
3090
3974
  .aegis-chat-close:hover{opacity:1}
3091
3975
  .aegis-chat-thread{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;background:#f8fafc}
3092
3976
  .aegis-chat-msg{display:flex;flex-direction:column;max-width:85%}
3093
3977
  .aegis-chat-msg--customer{align-self:flex-end;align-items:flex-end}
3094
3978
  .aegis-chat-msg--assistant,.aegis-chat-msg--system{align-self:flex-start;align-items:flex-start}
3095
3979
  .aegis-chat-bubbletext{padding:9px 13px;border-radius:14px;font-size:14px;line-height:1.45;white-space:pre-wrap;word-break:break-word}
3096
- .aegis-chat-msg--customer .aegis-chat-bubbletext{background:var(--aegis-chat-accent,#4169e1);color:#fff;border-bottom-right-radius:4px}
3980
+ .aegis-chat-msg--customer .aegis-chat-bubbletext{background:var(--aegis-chat-fill,var(--aegis-chat-accent,#4169e1));color:var(--aegis-chat-fill-fg,#fff);border-bottom-right-radius:4px}
3097
3981
  .aegis-chat-msg--assistant .aegis-chat-bubbletext{background:#fff;color:#0f172a;border:1px solid #e2e8f0;border-bottom-left-radius:4px}
3098
3982
  .aegis-chat-quickreplies{display:flex;flex-wrap:wrap;gap:6px;padding:2px 4px 6px;justify-content:flex-start}
3099
3983
  .aegis-chat-chip{appearance:none;cursor:pointer;border:1px solid var(--aegis-chat-accent,#4169e1);background:#fff;color:var(--aegis-chat-accent,#4169e1);border-radius:9999px;padding:6px 12px;font-size:13px;line-height:1;font-family:inherit;transition:background .15s,color .15s}
@@ -3699,6 +4583,7 @@ function renderActiveWebChat(ctx, creds) {
3699
4583
  icon: icon === "sparkle" || icon === "chat" || icon === "logo" ? icon : void 0,
3700
4584
  logoUrl: logo ? ctx.sanitizeUrl(logo) ?? void 0 : void 0,
3701
4585
  position: ic.chat_position === "bottom-left" ? "bottom-left" : void 0,
4586
+ surfaceStyle: ic.chat_theme === "tinted" ? "tinted" : void 0,
3702
4587
  // 'nav' = headless (no bubble); the host opens it via aegis.chat.open() or a
3703
4588
  // client_trigger. Defaults to 'bubble'. Per-device override via device_type
3704
4589
  // targeting on the campaign (e.g. a mobile campaign with chat_display_mode='nav').
@@ -3772,10 +4657,15 @@ const _AegisInAppManager = class _AegisInAppManager {
3772
4657
  this.ready = new Promise((resolve) => {
3773
4658
  this.readyResolve = resolve;
3774
4659
  });
4660
+ this.pendingPreInitRefresh = false;
3775
4661
  this.currentSurface = null;
3776
4662
  this.currentScreen = null;
3777
4663
  this.currentLocationId = null;
3778
4664
  this.filledSlots = /* @__PURE__ */ new WeakSet();
4665
+ this.slotSignatures = /* @__PURE__ */ new WeakMap();
4666
+ this.slotTimers = /* @__PURE__ */ new WeakMap();
4667
+ this.slotObserver = null;
4668
+ this.slotRenderScheduled = false;
3779
4669
  this.writeKey = config.writeKey;
3780
4670
  this.apiHost = config.apiHost || "https://api.aegis.ai";
3781
4671
  this.userId = config.userId ?? readAnonIdFromStorage$1();
@@ -3887,6 +4777,11 @@ const _AegisInAppManager = class _AegisInAppManager {
3887
4777
  this.connectSSE();
3888
4778
  }
3889
4779
  this.isInitialized = true;
4780
+ if (this.pendingPreInitRefresh) {
4781
+ this.pendingPreInitRefresh = false;
4782
+ this.log("Re-fetching campaigns for surface/screen declared during init");
4783
+ void this.refreshCampaigns();
4784
+ }
3890
4785
  this.log("AegisInApp initialized successfully");
3891
4786
  }
3892
4787
  updateUserId(userId) {
@@ -3935,16 +4830,17 @@ const _AegisInAppManager = class _AegisInAppManager {
3935
4830
  * change `currentSurface`, so arbitrary events never poison the surface gate.
3936
4831
  */
3937
4832
  refreshOnEvent(eventName, isScreenDeclaration = false) {
3938
- if (!this.isInitialized) {
3939
- this.log(`refreshOnEvent(${eventName}) before initialize — ignored`);
3940
- return;
3941
- }
3942
4833
  if (eventName && _AegisInAppManager.KNOWN_SURFACES.has(eventName)) {
3943
4834
  this.currentSurface = eventName;
3944
4835
  }
3945
4836
  if (isScreenDeclaration && eventName && eventName !== "page_view") {
3946
4837
  this.currentScreen = eventName;
3947
4838
  }
4839
+ if (!this.isInitialized) {
4840
+ this.pendingPreInitRefresh = true;
4841
+ this.log(`refreshOnEvent(${eventName}) before initialize — surface captured, refetch deferred to initialize()`);
4842
+ return;
4843
+ }
3948
4844
  if (this.refreshDebounceTimer) {
3949
4845
  clearTimeout(this.refreshDebounceTimer);
3950
4846
  }
@@ -4162,7 +5058,7 @@ const _AegisInAppManager = class _AegisInAppManager {
4162
5058
  try {
4163
5059
  const context = new URLSearchParams({
4164
5060
  device_type: this.detectDeviceType(),
4165
- page_url: typeof window !== "undefined" ? window.location.pathname : "/"
5061
+ page_url: typeof window !== "undefined" ? window.location.pathname + window.location.search : "/"
4166
5062
  });
4167
5063
  context.set("is_new_user", this.isNewUser() ? "true" : "false");
4168
5064
  if (this.currentSurface) {
@@ -4320,28 +5216,75 @@ const _AegisInAppManager = class _AegisInAppManager {
4320
5216
  if (!c.surface || c.surface.length === 0) return screenOk;
4321
5217
  return screenOk || this.matchesCurrentSurface(c);
4322
5218
  }
5219
+ // Re-fill slots that mount after a poll (SPA route changes). Observes the DOM
5220
+ // once; when a new [data-aegis-slot] appears it re-runs renderIntoSlots from
5221
+ // the cached campaigns (idempotent — already-filled slots are skipped). Does
5222
+ // NOT loop on the SDK's own injections (those add content INTO a slot, not a
5223
+ // new slot element).
5224
+ ensureSlotObserver() {
5225
+ if (this.slotObserver || typeof MutationObserver === "undefined" || typeof document === "undefined") {
5226
+ return;
5227
+ }
5228
+ this.slotObserver = new MutationObserver((mutations) => {
5229
+ let sawSlot = false;
5230
+ for (const m2 of mutations) {
5231
+ m2.addedNodes.forEach((n) => {
5232
+ var _a, _b;
5233
+ if (sawSlot || n.nodeType !== 1) return;
5234
+ const el = n;
5235
+ 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]"))) {
5236
+ sawSlot = true;
5237
+ }
5238
+ });
5239
+ if (sawSlot) break;
5240
+ }
5241
+ if (!sawSlot || this.slotRenderScheduled) return;
5242
+ this.slotRenderScheduled = true;
5243
+ const run = () => {
5244
+ this.slotRenderScheduled = false;
5245
+ this.renderIntoSlots();
5246
+ };
5247
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
5248
+ else setTimeout(run, 0);
5249
+ });
5250
+ this.slotObserver.observe(document.body, { childList: true, subtree: true });
5251
+ }
4323
5252
  renderIntoSlots() {
4324
5253
  if (typeof document === "undefined") return;
5254
+ this.ensureSlotObserver();
4325
5255
  const slots = document.querySelectorAll("[data-aegis-slot]");
4326
5256
  if (slots.length === 0) return;
4327
5257
  const eligibleByCategory = /* @__PURE__ */ new Map();
5258
+ const seenIds = /* @__PURE__ */ new Set();
4328
5259
  for (const c of this.campaigns) {
4329
5260
  if (!this.matchesCurrentScreenOrSurface(c)) continue;
4330
5261
  const modes = c.delivery_modes;
4331
5262
  const category = c.widget_category;
4332
5263
  if (!modes || !modes.includes("embedded_card")) continue;
4333
5264
  if (!category) continue;
5265
+ if (seenIds.has(c.id)) continue;
5266
+ seenIds.add(c.id);
4334
5267
  const arr2 = eligibleByCategory.get(category);
4335
5268
  if (arr2) arr2.push(c);
4336
5269
  else eligibleByCategory.set(category, [c]);
4337
5270
  }
4338
5271
  if (eligibleByCategory.size === 0) return;
4339
5272
  slots.forEach((slot) => {
4340
- if (this.filledSlots.has(slot)) return;
4341
5273
  const key = slot.getAttribute("data-aegis-slot");
4342
5274
  if (!key) return;
4343
5275
  const list = eligibleByCategory.get(key);
4344
5276
  if (!list || list.length === 0) return;
5277
+ const sig = list.map((c) => `${c.id}:${c.assigned_variant_id ?? ""}`).join(",");
5278
+ if (this.filledSlots.has(slot) && this.slotSignatures.get(slot) === sig) return;
5279
+ const oldTimer = this.slotTimers.get(slot);
5280
+ if (oldTimer) {
5281
+ clearInterval(oldTimer);
5282
+ this.slotTimers.delete(slot);
5283
+ }
5284
+ slot.querySelectorAll(
5285
+ ":scope > [data-aegis-rotate-pane], :scope > [data-aegis-slot-rendered], :scope > [data-aegis-rotate-dots]"
5286
+ ).forEach((n) => n.remove());
5287
+ this.slotSignatures.set(slot, sig);
4345
5288
  slot.querySelectorAll(":scope > [data-aegis-slot-default]").forEach((d) => d.remove());
4346
5289
  const attrMs = parseInt(slot.getAttribute("data-aegis-slot-rotate") || "0", 10);
4347
5290
  const declaredMs = list.reduce((m2, c) => {
@@ -4350,10 +5293,15 @@ const _AegisInAppManager = class _AegisInAppManager {
4350
5293
  return Number.isFinite(v) && v > m2 ? v : m2;
4351
5294
  }, 0);
4352
5295
  const rotateMs = attrMs > 0 ? attrMs : declaredMs;
5296
+ const flush = slot.hasAttribute("data-aegis-slot-flush");
4353
5297
  if (rotateMs > 0 && list.length > 1) {
4354
- this.renderRotatingSlot(slot, list, rotateMs);
5298
+ this.renderRotatingSlot(slot, list, rotateMs, flush);
4355
5299
  } else {
4356
- this.renderCampaignIntoSlot(list[0], slot);
5300
+ const host = document.createElement("div");
5301
+ host.setAttribute("data-aegis-slot-rendered", list[0].id);
5302
+ slot.appendChild(host);
5303
+ this.renderCampaignIntoSlot(list[0], host);
5304
+ if (flush) this._flattenSlotCard(host);
4357
5305
  }
4358
5306
  this.filledSlots.add(slot);
4359
5307
  });
@@ -4366,7 +5314,7 @@ const _AegisInAppManager = class _AegisInAppManager {
4366
5314
  * is deferred at render and fired per campaign on FIRST visibility, so hidden
4367
5315
  * panes never over-count. One rendered pane → no rotation (just shows it).
4368
5316
  */
4369
- renderRotatingSlot(slot, campaigns, rotateMs) {
5317
+ renderRotatingSlot(slot, campaigns, rotateMs, flush = false) {
4370
5318
  const panes = [];
4371
5319
  for (const c of campaigns) {
4372
5320
  const pane = document.createElement("div");
@@ -4374,8 +5322,10 @@ const _AegisInAppManager = class _AegisInAppManager {
4374
5322
  pane.style.display = "none";
4375
5323
  slot.appendChild(pane);
4376
5324
  this.renderCampaignIntoSlot(c, pane, { deferImpression: true });
4377
- if (pane.childElementCount > 0) panes.push({ el: pane, campaign: c });
4378
- else pane.remove();
5325
+ if (pane.childElementCount > 0) {
5326
+ if (flush) this._flattenSlotCard(pane);
5327
+ panes.push({ el: pane, campaign: c });
5328
+ } else pane.remove();
4379
5329
  }
4380
5330
  if (panes.length === 0) return;
4381
5331
  const impressed = /* @__PURE__ */ new Set();
@@ -4386,17 +5336,75 @@ const _AegisInAppManager = class _AegisInAppManager {
4386
5336
  this.emit("campaign-shown", c);
4387
5337
  };
4388
5338
  let idx = 0;
4389
- panes[0].el.style.display = "";
4390
- markVisible(panes[0].campaign);
4391
- if (panes.length === 1) return;
4392
- const timer = setInterval(() => {
5339
+ const isDarkSurface = (c) => {
5340
+ var _a, _b;
5341
+ const ic = c.interactive_config || {};
5342
+ if (ic.surface_style === "dark") return true;
5343
+ const tplBg = (_b = (_a = ic.card_template) == null ? void 0 : _a.frame) == null ? void 0 : _b.bg;
5344
+ const bg = (typeof tplBg === "string" ? tplBg : "") || c.background_color || "";
5345
+ if (bg.startsWith("#")) {
5346
+ const h = bg.length === 4 ? bg.slice(1).split("").map((x) => x + x).join("") : bg.slice(1, 7);
5347
+ const r = parseInt(h.slice(0, 2), 16), g = parseInt(h.slice(2, 4), 16), b = parseInt(h.slice(4, 6), 16);
5348
+ if (![r, g, b].some(Number.isNaN)) return (0.299 * r + 0.587 * g + 0.114 * b) / 255 < 0.5;
5349
+ }
5350
+ return false;
5351
+ };
5352
+ const darkDots = panes.length > 0 && panes.every((p) => isDarkSurface(p.campaign));
5353
+ const dotInactive = darkDots ? "rgba(255,255,255,0.4)" : "rgba(15,23,42,0.2)";
5354
+ const dotActive = darkDots ? "#ffffff" : "var(--aegis-brand-accent, #4169e1)";
5355
+ const dots = [];
5356
+ if (panes.length > 1) {
5357
+ const bar = document.createElement("div");
5358
+ bar.setAttribute("data-aegis-rotate-dots", "");
5359
+ bar.setAttribute("role", "tablist");
5360
+ bar.setAttribute("aria-label", "Offers");
5361
+ bar.style.cssText = "display:flex;align-items:center;justify-content:center;gap:5px;padding:2px 0 8px;";
5362
+ panes.forEach((_, i) => {
5363
+ const dot = document.createElement("button");
5364
+ dot.type = "button";
5365
+ dot.setAttribute("role", "tab");
5366
+ dot.setAttribute("aria-label", `Offer ${i + 1} of ${panes.length}`);
5367
+ dot.style.cssText = `border:none;padding:0;cursor:pointer;height:6px;width:6px;border-radius:999px;background:${dotInactive};transition:width .25s ease,background .25s ease;`;
5368
+ dot.addEventListener("click", (e) => {
5369
+ e.stopPropagation();
5370
+ show(i, true);
5371
+ });
5372
+ dots.push(dot);
5373
+ bar.appendChild(dot);
5374
+ });
5375
+ slot.appendChild(bar);
5376
+ }
5377
+ const paintDots = () => {
5378
+ dots.forEach((d, i) => {
5379
+ const active = i === idx;
5380
+ d.style.width = active ? "16px" : "6px";
5381
+ d.style.background = active ? dotActive : dotInactive;
5382
+ d.setAttribute("aria-selected", active ? "true" : "false");
5383
+ });
5384
+ };
5385
+ let timer = null;
5386
+ const start = () => setInterval(() => {
5387
+ show(idx + 1);
5388
+ }, rotateMs);
5389
+ function show(i, fromClick = false) {
4393
5390
  panes[idx].el.style.display = "none";
4394
- idx = (idx + 1) % panes.length;
5391
+ idx = (i % panes.length + panes.length) % panes.length;
4395
5392
  panes[idx].el.style.display = "";
4396
5393
  markVisible(panes[idx].campaign);
4397
- }, rotateMs);
5394
+ paintDots();
5395
+ if (fromClick && timer) {
5396
+ clearInterval(timer);
5397
+ timer = start();
5398
+ }
5399
+ }
5400
+ panes[0].el.style.display = "";
5401
+ markVisible(panes[0].campaign);
5402
+ paintDots();
5403
+ if (panes.length === 1) return;
5404
+ timer = start();
5405
+ this.slotTimers.set(slot, timer);
4398
5406
  if (typeof window !== "undefined") {
4399
- window.addEventListener("beforeunload", () => clearInterval(timer), { once: true });
5407
+ window.addEventListener("beforeunload", () => timer && clearInterval(timer), { once: true });
4400
5408
  }
4401
5409
  }
4402
5410
  /**
@@ -4450,6 +5458,14 @@ const _AegisInAppManager = class _AegisInAppManager {
4450
5458
  target.querySelectorAll(":scope > [data-aegis-slot-default]").forEach((d) => d.remove());
4451
5459
  }
4452
5460
  }
5461
+ const spacing = Number(anchor == null ? void 0 : anchor.element_spacing);
5462
+ if (spacing > 0) {
5463
+ if (position === "before" || position === "after") {
5464
+ target.style.margin = `${spacing}px`;
5465
+ } else {
5466
+ target.style.padding = `${spacing}px`;
5467
+ }
5468
+ }
4453
5469
  if (this.filledSlots.has(target)) continue;
4454
5470
  this.renderCampaignIntoSlot(c, target);
4455
5471
  this.filledSlots.add(target);
@@ -4555,7 +5571,12 @@ const _AegisInAppManager = class _AegisInAppManager {
4555
5571
  }
4556
5572
  return;
4557
5573
  }
4558
- if (campaign.widget_category === "hero") {
5574
+ const _tpl = ic.card_template;
5575
+ const _hasComposedTemplate = !!_tpl && typeof _tpl === "object" && Array.isArray(_tpl.elements);
5576
+ const _isMultiCard = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
5577
+ if (_hasComposedTemplate && !_isMultiCard) {
5578
+ rendered = this.renderComposedCardSlot(campaign, ic, target);
5579
+ } else if (campaign.widget_category === "hero") {
4559
5580
  rendered = renderHeroInline(this.buildRenderContext(campaign), target);
4560
5581
  } else
4561
5582
  switch (campaign.sub_type) {
@@ -4608,6 +5629,9 @@ const _AegisInAppManager = class _AegisInAppManager {
4608
5629
  case "progress_bar":
4609
5630
  rendered = renderProgressBarInline(this.buildRenderContext(campaign), target);
4610
5631
  break;
5632
+ case "product_recommendation":
5633
+ rendered = renderProductRecommendationInline(this.buildRenderContext(campaign), target);
5634
+ break;
4611
5635
  case "quiz":
4612
5636
  rendered = this.renderQuizSlot(campaign, ic, bg, text, target);
4613
5637
  if (!rendered) {
@@ -4670,6 +5694,85 @@ const _AegisInAppManager = class _AegisInAppManager {
4670
5694
  * The catch-all for banner + nudge sub_types (birthday, referral, app-install,
4671
5695
  * win-back, etc.) so EVERY gallery nudge renders on the bill (previously only
4672
5696
  * star_rating / nps_survey / form were handled → nudges never appeared). */
5697
+ /**
5698
+ * Composed single card — renders a campaign's top-level `card_template`
5699
+ * (frame + elements: icon/text/progress/image/cta/group/…) through the generic
5700
+ * `renderCardFromTemplate` engine. The canonical path for the rich card editor:
5701
+ * the storefront value bar, loyalty nudges, and any operator-composed card all
5702
+ * render here from declarative JSON, on ACS / custom / third-party sites.
5703
+ */
5704
+ renderComposedCardSlot(campaign, ic, target) {
5705
+ const c = campaign;
5706
+ const card = {
5707
+ image_url: typeof c.image_url === "string" ? c.image_url : void 0,
5708
+ title: campaign.title,
5709
+ body: campaign.body,
5710
+ cta_text: typeof c.button_text === "string" ? c.button_text : void 0,
5711
+ cta_url: typeof c.action_url === "string" ? c.action_url : void 0,
5712
+ metadata: ic.card_meta || {}
5713
+ };
5714
+ const template = resolveGlobalTemplate(ic);
5715
+ const { accent } = this._surfacePalette(campaign);
5716
+ const tile = renderCardFromTemplate(card, {
5717
+ template,
5718
+ ctx: this.buildRenderContext(campaign),
5719
+ action: "navigate",
5720
+ layoutAccent: this.sanitizeColor(campaign.background_color || accent)
5721
+ });
5722
+ target.appendChild(tile);
5723
+ return true;
5724
+ }
5725
+ /**
5726
+ * C2 — composed card body in an OVERLAY (modal / banner / full_screen /
5727
+ * half_interstitial). The overlay counterpart to `renderComposedCardSlot`: it
5728
+ * builds the same `TemplateCard` from the campaign's canonical fields and paints
5729
+ * it with the generic `renderCardFromTemplate` engine, then wraps it in the
5730
+ * standard overlay chrome (close button + size scaling) so a composed card looks
5731
+ * identical whether overlaid or slotted. Only invoked when a `card_template` is
5732
+ * present (see displayCampaign) — flat campaigns never reach here.
5733
+ * See IN_APP_FORMAT_LOCK_AND_COMPOSABLE_CARDS_TRACKER.md (Part B / C2).
5734
+ */
5735
+ renderComposedCardOverlay(campaign, ic) {
5736
+ const { accent } = this._surfacePalette(campaign);
5737
+ const bg = this.sanitizeColor(campaign.background_color || "#ffffff");
5738
+ const text = this.sanitizeColor(campaign.text_color || "#111827");
5739
+ const fullScreen = campaign.type === "full_screen";
5740
+ const overlay = this.createOverlay("aegis-in-app-composed-card-overlay");
5741
+ const modal = document.createElement("div");
5742
+ modal.style.cssText = fullScreen ? `position: relative; width: 100%; height: 100%; max-width: 100%; overflow: auto;
5743
+ background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;` : `position: relative; max-width: 380px; width: 92%; border-radius: 18px; overflow: hidden;
5744
+ background: ${bg}; color: ${text}; box-shadow: 0 16px 48px rgba(0,0,0,0.22);
5745
+ animation: aegisScaleIn 0.3s ease;`;
5746
+ const c = campaign;
5747
+ const card = {
5748
+ image_url: typeof c.image_url === "string" ? c.image_url : void 0,
5749
+ title: campaign.title,
5750
+ body: campaign.body,
5751
+ cta_text: typeof c.button_text === "string" ? c.button_text : void 0,
5752
+ cta_url: typeof c.action_url === "string" ? c.action_url : void 0,
5753
+ metadata: ic.card_meta || {}
5754
+ };
5755
+ const template = resolveGlobalTemplate(ic);
5756
+ const tile = renderCardFromTemplate(card, {
5757
+ template,
5758
+ ctx: this.buildRenderContext(campaign),
5759
+ action: "navigate",
5760
+ layoutAccent: this.sanitizeColor(campaign.background_color || accent)
5761
+ });
5762
+ modal.appendChild(tile);
5763
+ this._addCornerClose(modal, overlay, campaign.id, text);
5764
+ if (fullScreen) {
5765
+ overlay.appendChild(modal);
5766
+ } else {
5767
+ const mul = this._sizeMul(ic);
5768
+ const sizer = document.createElement("div");
5769
+ sizer.style.cssText = `display: flex; justify-content: center; align-items: center; width: 100%; transform: scale(${mul}); transform-origin: center;`;
5770
+ sizer.appendChild(modal);
5771
+ overlay.appendChild(sizer);
5772
+ }
5773
+ this.addAnimationStyles();
5774
+ document.body.appendChild(overlay);
5775
+ }
4673
5776
  renderGenericCardSlot(campaign, ic, bg, text, target) {
4674
5777
  const st = this._slotStyle(campaign);
4675
5778
  const card = this._wrapInSlotCard("aegis-in-app-generic-card", campaign.id, bg, text, st.radius);
@@ -4685,6 +5788,45 @@ const _AegisInAppManager = class _AegisInAppManager {
4685
5788
  im.style.cssText = "width: 100%; height: 96px; object-fit: cover; display: block;";
4686
5789
  card.appendChild(im);
4687
5790
  }
5791
+ const widgetCat = typeof c.widget_category === "string" ? c.widget_category : "";
5792
+ if (widgetCat === "promo" && !img) {
5793
+ const row = document.createElement("div");
5794
+ row.style.cssText = `display:flex; align-items:center; gap:12px; min-height:46px; padding:${st.padY ?? 10}px ${st.padX ?? 14}px;`;
5795
+ const colL = document.createElement("div");
5796
+ colL.style.cssText = "flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px;";
5797
+ if (campaign.title) {
5798
+ const t = document.createElement("div");
5799
+ t.style.cssText = `font-size:13px; font-weight:${st.weight ?? "700"}; line-height:1.25; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;`;
5800
+ t.textContent = campaign.title;
5801
+ colL.appendChild(t);
5802
+ }
5803
+ if (campaign.body) {
5804
+ const b = document.createElement("div");
5805
+ b.style.cssText = "font-size:11.5px; opacity:0.7; line-height:1.3; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;";
5806
+ b.textContent = campaign.body;
5807
+ colL.appendChild(b);
5808
+ }
5809
+ row.appendChild(colL);
5810
+ if (buttonText) {
5811
+ const btn = document.createElement("button");
5812
+ btn.type = "button";
5813
+ btn.textContent = buttonText;
5814
+ const accentTok = `var(--aegis-brand-accent, ${text})`;
5815
+ 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;`;
5816
+ btn.addEventListener("click", () => {
5817
+ void this.trackEvent(campaign.id, "clicked");
5818
+ if (actionUrl) {
5819
+ window.open(actionUrl, "_blank", "noopener");
5820
+ } else if (typeof window !== "undefined") {
5821
+ window.dispatchEvent(new CustomEvent("aegis:open-login", { detail: { campaign_id: campaign.id } }));
5822
+ }
5823
+ });
5824
+ row.appendChild(btn);
5825
+ }
5826
+ card.appendChild(row);
5827
+ target.appendChild(card);
5828
+ return true;
5829
+ }
4688
5830
  const cbody = document.createElement("div");
4689
5831
  cbody.style.cssText = `padding: ${st.padY ?? 12}px ${st.padX ?? 12}px; display: flex; flex-direction: column; gap: 6px;`;
4690
5832
  if (campaign.title) {
@@ -4828,8 +5970,14 @@ const _AegisInAppManager = class _AegisInAppManager {
4828
5970
  body.style.alignItems = "center";
4829
5971
  body.style.justifyContent = "center";
4830
5972
  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.";
5973
+ 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;";
5974
+ const customThanks = typeof ((_a = campaign.interactive_config) == null ? void 0 : _a.thank_you_message) === "string" ? campaign.interactive_config.thank_you_message : null;
5975
+ if (customThanks) {
5976
+ done.textContent = customThanks;
5977
+ } else {
5978
+ done.innerHTML = lucideSvg("check", { size: 14 });
5979
+ done.appendChild(document.createTextNode("Thanks — your details are saved."));
5980
+ }
4833
5981
  body.appendChild(done);
4834
5982
  this._playReaction(body, "affirm", "check.json", campaign.interactive_config);
4835
5983
  });
@@ -5169,11 +6317,11 @@ const _AegisInAppManager = class _AegisInAppManager {
5169
6317
  const soundBtn = document.createElement("button");
5170
6318
  soundBtn.type = "button";
5171
6319
  soundBtn.setAttribute("aria-label", "Toggle sound");
5172
- soundBtn.textContent = soundOn ? "🔊" : "🔇";
6320
+ soundBtn.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
5173
6321
  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
6322
  soundBtn.addEventListener("click", () => {
5175
6323
  soundOn = !soundOn;
5176
- soundBtn.textContent = soundOn ? "🔊" : "🔇";
6324
+ soundBtn.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
5177
6325
  });
5178
6326
  card.appendChild(soundBtn);
5179
6327
  }
@@ -5288,7 +6436,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5288
6436
  var _a;
5289
6437
  try {
5290
6438
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(r.code));
5291
- chip.textContent = "Copied ✓";
6439
+ chip.textContent = "Copied";
5292
6440
  } catch {
5293
6441
  }
5294
6442
  });
@@ -5334,7 +6482,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5334
6482
  var _a;
5335
6483
  try {
5336
6484
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(r.code));
5337
- chip.textContent = "Copied ✓";
6485
+ chip.textContent = "Copied";
5338
6486
  } catch {
5339
6487
  }
5340
6488
  });
@@ -5575,7 +6723,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5575
6723
  pctLabel.textContent = `${pct}%`;
5576
6724
  pctLabel.style.opacity = "0.8";
5577
6725
  } else if (pctLabel && i === selected) {
5578
- pctLabel.textContent = "✓";
6726
+ pctLabel.innerHTML = lucideSvg("check", { size: 14 });
5579
6727
  pctLabel.style.opacity = "0.8";
5580
6728
  }
5581
6729
  });
@@ -5598,59 +6746,18 @@ const _AegisInAppManager = class _AegisInAppManager {
5598
6746
  }
5599
6747
  const strip = document.createElement("div");
5600
6748
  strip.style.cssText = `display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory; padding: ${st.padY ?? 12}px ${st.padX ?? 12}px;`;
6749
+ const template = resolveGlobalTemplate(ic);
6750
+ const ctx = this.buildRenderContext(campaign);
6751
+ const accent = this.sanitizeColor(campaign.background_color || text);
5601
6752
  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);
6753
+ const tile = renderCardFromTemplate(c, {
6754
+ template,
6755
+ ctx,
6756
+ action: "navigate",
6757
+ layout: "row",
6758
+ layoutAccent: accent
6759
+ });
6760
+ tile.setAttribute("data-card-index", String(i));
5654
6761
  strip.appendChild(tile);
5655
6762
  });
5656
6763
  card.appendChild(strip);
@@ -5902,11 +7009,11 @@ const _AegisInAppManager = class _AegisInAppManager {
5902
7009
  const sb = document.createElement("button");
5903
7010
  sb.type = "button";
5904
7011
  sb.setAttribute("aria-label", "Toggle sound");
5905
- sb.textContent = soundOn ? "🔊" : "🔇";
7012
+ sb.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
5906
7013
  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
7014
  sb.addEventListener("click", () => {
5908
7015
  soundOn = !soundOn;
5909
- sb.textContent = soundOn ? "🔊" : "🔇";
7016
+ sb.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
5910
7017
  });
5911
7018
  card.appendChild(sb);
5912
7019
  }
@@ -6075,7 +7182,7 @@ const _AegisInAppManager = class _AegisInAppManager {
6075
7182
  var _a;
6076
7183
  try {
6077
7184
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(code));
6078
- chip.textContent = "Copied ✓";
7185
+ chip.textContent = "Copied";
6079
7186
  } catch {
6080
7187
  }
6081
7188
  });
@@ -6263,6 +7370,19 @@ const _AegisInAppManager = class _AegisInAppManager {
6263
7370
  `;
6264
7371
  return card;
6265
7372
  }
7373
+ /**
7374
+ * Flush a rendered slot card into its host card — drop the per-campaign radius
7375
+ * + shadow so it reads as one unit with the host wrapper (used for the
7376
+ * storefront value bar via `data-aegis-slot-flush`). Targets every
7377
+ * `[data-campaign-id]` card inside `container` (or the container itself).
7378
+ */
7379
+ _flattenSlotCard(container) {
7380
+ const cards = container.matches("[data-campaign-id]") ? [container] : Array.from(container.querySelectorAll("[data-campaign-id]"));
7381
+ for (const el of cards) {
7382
+ el.style.borderRadius = "0";
7383
+ el.style.boxShadow = "none";
7384
+ }
7385
+ }
6266
7386
  // Optional header/brand image atop a survey-style widget (star / nps).
6267
7387
  _appendHeaderImage(body, ic) {
6268
7388
  const url = ic.header_image_url;
@@ -6704,6 +7824,9 @@ const _AegisInAppManager = class _AegisInAppManager {
6704
7824
  }
6705
7825
  }
6706
7826
  displayCampaign(campaign) {
7827
+ if (Array.isArray(campaign.delivery_modes) && campaign.delivery_modes.length > 0 && !campaign.delivery_modes.includes("in_app_overlay")) {
7828
+ return;
7829
+ }
6707
7830
  const proceed = this.emit("campaign-will-show", campaign);
6708
7831
  if (!proceed) {
6709
7832
  this.log(`campaign ${campaign.id} suppressed by campaign-will-show handler`);
@@ -6734,6 +7857,19 @@ const _AegisInAppManager = class _AegisInAppManager {
6734
7857
  this.emit("campaign-shown", campaign);
6735
7858
  return;
6736
7859
  }
7860
+ {
7861
+ const ic2 = campaign.interactive_config || {};
7862
+ const tpl2 = ic2.card_template;
7863
+ const hasTpl2 = !!tpl2 && typeof tpl2 === "object" && Array.isArray(tpl2.elements) && tpl2.elements.length > 0;
7864
+ const multiCard2 = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
7865
+ const overlayCardType = campaign.type === "modal" || campaign.type === "banner" || campaign.type === "full_screen" || campaign.type === "half_interstitial";
7866
+ if (hasTpl2 && !multiCard2 && overlayCardType) {
7867
+ this.renderComposedCardOverlay(campaign, ic2);
7868
+ this.trackEvent(campaign.id, "impression");
7869
+ this.emit("campaign-shown", campaign);
7870
+ return;
7871
+ }
7872
+ }
6737
7873
  switch (campaign.type) {
6738
7874
  case "custom_html":
6739
7875
  this.renderCustomHtml(campaign);
@@ -6833,6 +7969,16 @@ const _AegisInAppManager = class _AegisInAppManager {
6833
7969
  return {
6834
7970
  campaign,
6835
7971
  getCartState: this.getCartState,
7972
+ properties: () => {
7973
+ var _a, _b;
7974
+ const cart = (_a = this.getCartState) == null ? void 0 : _a.call(this);
7975
+ const ctxObj = ((_b = campaign.interactive_config) == null ? void 0 : _b.card_context) || {};
7976
+ return {
7977
+ "cart.total": cart == null ? void 0 : cart.total,
7978
+ "cart.count": cart == null ? void 0 : cart.itemCount,
7979
+ ...ctxObj
7980
+ };
7981
+ },
6836
7982
  trackEvent: (id, evt, extra) => {
6837
7983
  void this.trackEvent(id, evt, extra);
6838
7984
  },
@@ -6943,7 +8089,7 @@ const _AegisInAppManager = class _AegisInAppManager {
6943
8089
  '<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
8090
  );
6945
8091
  if (closeIcon) x.appendChild(closeIcon);
6946
- else x.textContent = "✕";
8092
+ else x.innerHTML = lucideSvg("x", { size: 14 });
6947
8093
  x.style.cssText = `
6948
8094
  position: absolute; top: 12px; right: 12px; z-index: 4; width: 28px; height: 28px;
6949
8095
  display: flex; align-items: center; justify-content: center; border: none;
@@ -7781,12 +8927,17 @@ const _AegisInAppManager = class _AegisInAppManager {
7781
8927
  _surfacePalette(campaign) {
7782
8928
  const ic = campaign.interactive_config || {};
7783
8929
  const brand = this.sanitizeColor(campaign.background_color || "#4169e1");
7784
- const branded = ic.surface_style === "branded";
7785
- if (branded) {
8930
+ const surface = ic.surface_style;
8931
+ if (surface === "branded") {
7786
8932
  const bg = brand;
7787
8933
  const text = campaign.text_color ? this.sanitizeColor(campaign.text_color) : "#ffffff";
7788
8934
  return { bg, text, btnBg: text, btnText: bg, accent: text, branded: true };
7789
8935
  }
8936
+ if (surface === "dark") {
8937
+ const bg = this.sanitizeColor(campaign.background_color || "#0f172a");
8938
+ const text = campaign.text_color ? this.sanitizeColor(campaign.text_color) : "#ffffff";
8939
+ return { bg, text, btnBg: "#ffffff", btnText: bg, accent: text, branded: false };
8940
+ }
7790
8941
  return { bg: "#ffffff", text: "#0f172a", btnBg: brand, btnText: this._contrastText(brand), accent: brand, branded: false };
7791
8942
  }
7792
8943
  inAppStyle(campaign) {
@@ -7890,7 +9041,7 @@ const _AegisInAppManager = class _AegisInAppManager {
7890
9041
  actionsContainer.appendChild(ctaButton);
7891
9042
  }
7892
9043
  const closeButton = document.createElement("button");
7893
- closeButton.textContent = "✕";
9044
+ closeButton.innerHTML = lucideSvg("x", { size: 18 });
7894
9045
  closeButton.setAttribute("aria-label", "Close");
7895
9046
  closeButton.style.cssText = `
7896
9047
  background: transparent;
@@ -8088,7 +9239,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8088
9239
  pill.replaceChildren();
8089
9240
  const ic = document.createElement("span");
8090
9241
  ic.style.fontSize = "13px";
8091
- ic.textContent = v.muted ? "🔇" : "🔊";
9242
+ ic.innerHTML = lucideSvg(v.muted ? "volume-x" : "volume-2", { size: 13 });
8092
9243
  pill.appendChild(ic);
8093
9244
  if (v.muted) {
8094
9245
  const t = document.createElement("span");
@@ -8218,7 +9369,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8218
9369
  }
8219
9370
  overlay.appendChild(contentContainer);
8220
9371
  const closeButton = document.createElement("button");
8221
- closeButton.textContent = "✕";
9372
+ closeButton.innerHTML = lucideSvg("x", { size: isBg ? 18 : 28 });
8222
9373
  closeButton.setAttribute("aria-label", "Close");
8223
9374
  closeButton.style.cssText = `
8224
9375
  position: absolute; top: 20px; right: 20px; z-index: 3; background: ${isBg ? "rgba(0,0,0,0.45)" : "transparent"};
@@ -8346,7 +9497,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8346
9497
  }
8347
9498
  modal.appendChild(content);
8348
9499
  const closeButton = document.createElement("button");
8349
- closeButton.textContent = "✕";
9500
+ closeButton.innerHTML = lucideSvg("x", { size: 22 });
8350
9501
  closeButton.setAttribute("aria-label", "Close");
8351
9502
  closeButton.style.cssText = `
8352
9503
  position: absolute;
@@ -8419,16 +9570,16 @@ const _AegisInAppManager = class _AegisInAppManager {
8419
9570
  const ic = campaign.interactive_config || {};
8420
9571
  const variant = ic.alert_variant || "";
8421
9572
  const VARIANTS = {
8422
- info: { glyph: "ℹ", color: "#3b82f6" },
8423
- success: { glyph: "✓", color: "#10b981" },
8424
- warning: { glyph: "!", color: "#f59e0b" },
8425
- error: { glyph: "✕", color: "#ef4444" }
9573
+ info: { icon: "info", color: "#3b82f6" },
9574
+ success: { icon: "check", color: "#10b981" },
9575
+ warning: { icon: "triangle-alert", color: "#f59e0b" },
9576
+ error: { icon: "x", color: "#ef4444" }
8426
9577
  };
8427
9578
  const vspec = VARIANTS[variant];
8428
9579
  if (vspec) {
8429
9580
  const badge = document.createElement("div");
8430
9581
  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;
9582
+ badge.innerHTML = lucideSvg(vspec.icon, { size: 24 });
8432
9583
  content.appendChild(badge);
8433
9584
  }
8434
9585
  const title = document.createElement("h3");
@@ -8570,7 +9721,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8570
9721
  if (!hasMedia) {
8571
9722
  const placeholder = document.createElement("div");
8572
9723
  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 = "▶";
9724
+ placeholder.innerHTML = lucideSvg("play", { size: 34 });
8574
9725
  mediaWrap.appendChild(placeholder);
8575
9726
  }
8576
9727
  pip.appendChild(mediaWrap);
@@ -8581,7 +9732,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8581
9732
  playGlyph.setAttribute("aria-label", "Play or pause");
8582
9733
  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
9734
  const syncGlyph = () => {
8584
- playGlyph.textContent = v.paused ? "▶" : "❚❚";
9735
+ playGlyph.innerHTML = lucideSvg(v.paused ? "play" : "pause", { size: 22 });
8585
9736
  playGlyph.style.opacity = v.paused ? "1" : "0";
8586
9737
  };
8587
9738
  const togglePlay = () => {
@@ -8608,7 +9759,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8608
9759
  mutePill.replaceChildren();
8609
9760
  const ic2 = document.createElement("span");
8610
9761
  ic2.style.fontSize = "13px";
8611
- ic2.textContent = v.muted ? "🔇" : "🔊";
9762
+ ic2.innerHTML = lucideSvg(v.muted ? "volume-x" : "volume-2", { size: 13 });
8612
9763
  mutePill.appendChild(ic2);
8613
9764
  if (v.muted) {
8614
9765
  const t = document.createElement("span");
@@ -8703,7 +9854,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8703
9854
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
8704
9855
  `;
8705
9856
  if (expandBtn) {
8706
- expandBtn.textContent = "✕";
9857
+ expandBtn.innerHTML = lucideSvg("x", { size: 16 });
8707
9858
  expandBtn.setAttribute("aria-label", "Minimize");
8708
9859
  }
8709
9860
  if (video && video.muted) {
@@ -8726,7 +9877,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8726
9877
  }
8727
9878
  const closeButton = document.createElement("button");
8728
9879
  closeButton.type = "button";
8729
- closeButton.textContent = "✕";
9880
+ closeButton.innerHTML = lucideSvg("x", { size: 16 });
8730
9881
  closeButton.setAttribute("aria-label", "Close");
8731
9882
  closeButton.style.cssText = ctrlBtnCss;
8732
9883
  closeButton.addEventListener("click", (e) => {
@@ -9746,13 +10897,29 @@ function evaluateRule(rule, snapshot, namespaces) {
9746
10897
  const result = evaluateExpr(rule.when, snapshot, namespaces);
9747
10898
  return result === true;
9748
10899
  }
9749
- class IntentRuleEvaluator {
10900
+ const _IntentRuleEvaluator = class _IntentRuleEvaluator {
9750
10901
  constructor() {
9751
10902
  this.armed = [];
9752
10903
  this.snapshot = {};
9753
10904
  this.namespaces = {};
9754
10905
  this.firedThisSession = /* @__PURE__ */ new Set();
9755
10906
  this.silencedThisSession = /* @__PURE__ */ new Set();
10907
+ this.oneInterruptionPerSession = false;
10908
+ this.interruptiveBudgetSpent = false;
10909
+ this.ttfiMs = 0;
10910
+ this.sessionStartMs = Date.now();
10911
+ this.nowFn = () => Date.now();
10912
+ }
10913
+ /** Tune governance. `now` also (re)bases the TTFI session clock. */
10914
+ setGovernance(opts) {
10915
+ if (opts.oneInterruptionPerSession !== void 0) {
10916
+ this.oneInterruptionPerSession = opts.oneInterruptionPerSession;
10917
+ }
10918
+ if (opts.ttfiMs !== void 0) this.ttfiMs = opts.ttfiMs;
10919
+ if (opts.now) {
10920
+ this.nowFn = opts.now;
10921
+ this.sessionStartMs = opts.now();
10922
+ }
9756
10923
  }
9757
10924
  // ─── snapshot management ───
9758
10925
  updateSignal(signal, value) {
@@ -9788,12 +10955,15 @@ class IntentRuleEvaluator {
9788
10955
  }
9789
10956
  markFired(campaignId) {
9790
10957
  this.firedThisSession.add(campaignId);
10958
+ this.interruptiveBudgetSpent = true;
9791
10959
  }
9792
10960
  /** Clear all per-session state. Caller invokes on logout or explicit
9793
10961
  * session reset. */
9794
10962
  reset() {
9795
10963
  this.firedThisSession.clear();
9796
10964
  this.silencedThisSession.clear();
10965
+ this.interruptiveBudgetSpent = false;
10966
+ this.sessionStartMs = this.nowFn();
9797
10967
  }
9798
10968
  // ─── the main dispatch ───
9799
10969
  /**
@@ -9816,6 +10986,11 @@ class IntentRuleEvaluator {
9816
10986
  * 5. If nothing matches, return 'none'.
9817
10987
  */
9818
10988
  onSignalChanged(signal) {
10989
+ if (this.oneInterruptionPerSession && this.interruptiveBudgetSpent) {
10990
+ return { kind: "none" };
10991
+ }
10992
+ const sessionElapsedMs = this.nowFn() - this.sessionStartMs;
10993
+ const exemptFromTtfi = _IntentRuleEvaluator.EXIT_SIGNALS.includes(signal);
9819
10994
  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
10995
  if (b.rule.priority !== a.rule.priority) {
9821
10996
  return b.rule.priority - a.rule.priority;
@@ -9825,6 +11000,9 @@ class IntentRuleEvaluator {
9825
11000
  for (let i = 0; i < candidates.length; i++) {
9826
11001
  const c = candidates[i];
9827
11002
  if (!evaluateRule(c.rule, this.snapshot, this.namespaces)) continue;
11003
+ if (this.ttfiMs > 0 && sessionElapsedMs < this.ttfiMs && !exemptFromTtfi) {
11004
+ continue;
11005
+ }
9828
11006
  if (c.rule.suppress_competing) {
9829
11007
  const suppressIds = this.armed.filter((other) => other.id !== c.id).map((other) => other.id);
9830
11008
  suppressIds.forEach((id) => this.silencedThisSession.add(id));
@@ -9838,7 +11016,12 @@ class IntentRuleEvaluator {
9838
11016
  }
9839
11017
  return { kind: "none" };
9840
11018
  }
9841
- }
11019
+ };
11020
+ _IntentRuleEvaluator.EXIT_SIGNALS = [
11021
+ "exit_intent",
11022
+ "back_button"
11023
+ ];
11024
+ let IntentRuleEvaluator = _IntentRuleEvaluator;
9842
11025
  class ContactScoresFetcher {
9843
11026
  constructor(cfg) {
9844
11027
  this.lastFetchedAt = 0;
@@ -10192,7 +11375,7 @@ class PrefetchBundleClient {
10192
11375
  }
10193
11376
  let response;
10194
11377
  try {
10195
- response = await fetch(url, { method: "GET", headers });
11378
+ response = await fetch(url, { method: "GET", headers, cache: "no-cache" });
10196
11379
  } catch (err) {
10197
11380
  logger.warn("prefetch-bundle fetch network error:", err);
10198
11381
  return this.currentBundle;
@@ -11217,8 +12400,8 @@ class AegisWidgetManager {
11217
12400
  { label: "Prize 3", color: "#48dbfb" },
11218
12401
  { label: "Prize 4", color: "#ff6348" }
11219
12402
  ];
11220
- const SVG_NS = "http://www.w3.org/2000/svg";
11221
- const wheel = document.createElementNS(SVG_NS, "svg");
12403
+ const SVG_NS2 = "http://www.w3.org/2000/svg";
12404
+ const wheel = document.createElementNS(SVG_NS2, "svg");
11222
12405
  wheel.setAttribute("viewBox", "-160 -160 320 320");
11223
12406
  wheel.setAttribute("width", "300");
11224
12407
  wheel.setAttribute("height", "300");
@@ -11234,7 +12417,7 @@ class AegisWidgetManager {
11234
12417
  const x2 = Math.cos(end) * radius;
11235
12418
  const y2 = Math.sin(end) * radius;
11236
12419
  const largeArc = anglePer > Math.PI ? 1 : 0;
11237
- const path = document.createElementNS(SVG_NS, "path");
12420
+ const path = document.createElementNS(SVG_NS2, "path");
11238
12421
  path.setAttribute(
11239
12422
  "d",
11240
12423
  `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 +12429,7 @@ class AegisWidgetManager {
11246
12429
  const labelAngle = start + anglePer / 2;
11247
12430
  const lx = Math.cos(labelAngle) * radius * 0.65;
11248
12431
  const ly = Math.sin(labelAngle) * radius * 0.65;
11249
- const text = document.createElementNS(SVG_NS, "text");
12432
+ const text = document.createElementNS(SVG_NS2, "text");
11250
12433
  text.setAttribute("x", lx.toFixed(2));
11251
12434
  text.setAttribute("y", ly.toFixed(2));
11252
12435
  text.setAttribute("fill", "#ffffff");
@@ -11358,7 +12541,8 @@ class AegisWidgetManager {
11358
12541
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
11359
12542
  `;
11360
12543
  const closeBtn = document.createElement("button");
11361
- closeBtn.innerHTML = "✕";
12544
+ closeBtn.innerHTML = lucideSvg("x", { size: 16 });
12545
+ closeBtn.setAttribute("aria-label", "Close");
11362
12546
  closeBtn.className = "aegis-spin-wheel-close";
11363
12547
  closeBtn.style.cssText = `
11364
12548
  position: absolute;
@@ -12338,8 +13522,9 @@ class AegisWidgetManager {
12338
13522
  }
12339
13523
  if (config.show_timer && config.timer_minutes) {
12340
13524
  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`;
13525
+ timer.style.cssText = "margin: 0 0 24px 0; display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 14px; color: #999;";
13526
+ timer.innerHTML = lucideSvg("clock", { size: 14 });
13527
+ timer.appendChild(document.createTextNode(`Offer expires in ${config.timer_minutes} minutes`));
12343
13528
  modal.appendChild(timer);
12344
13529
  }
12345
13530
  const ctaButton = document.createElement("button");
@@ -12470,7 +13655,7 @@ class AegisWidgetManager {
12470
13655
  });
12471
13656
  modal.innerHTML = `
12472
13657
  <div style="text-align: center; padding: 20px;">
12473
- <div style="font-size: 48px; margin-bottom: 16px;">✅</div>
13658
+ <div style="margin-bottom: 16px; color: #10b981;">${lucideSvg("check", { size: 48 })}</div>
12474
13659
  <h3 style="margin: 0 0 12px 0; color: #1a73e8;">Thank You!</h3>
12475
13660
  <p style="margin: 0; color: #666;">Check your email for your discount code!</p>
12476
13661
  </div>
@@ -12716,6 +13901,9 @@ class AegisMessageRuntime {
12716
13901
  getCartState: () => this.deriveCartState()
12717
13902
  });
12718
13903
  this.intentRuleEvaluator = new IntentRuleEvaluator();
13904
+ if (config.governance) {
13905
+ this.intentRuleEvaluator.setGovernance(config.governance);
13906
+ }
12719
13907
  this.contactScores = new ContactScoresFetcher({
12720
13908
  // Falls back to same-origin when no apiHost configured (storefront
12721
13909
  // typical case) — matches WidgetManager/InAppManager handling