@active-reach/web-sdk 1.20.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) 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/cdn.d.ts.map +1 -1
  7. package/dist/chat/AegisChat.d.ts +24 -0
  8. package/dist/chat/AegisChat.d.ts.map +1 -1
  9. package/dist/chat/index.d.ts +1 -1
  10. package/dist/chat/index.d.ts.map +1 -1
  11. package/dist/core/prefetch-bundle-client.d.ts.map +1 -1
  12. package/dist/inapp/AegisInAppManager.d.ts +38 -1
  13. package/dist/inapp/AegisInAppManager.d.ts.map +1 -1
  14. package/dist/inapp/lucide-svg.d.ts +46 -0
  15. package/dist/inapp/lucide-svg.d.ts.map +1 -0
  16. package/dist/inapp/renderPreview.d.ts +16 -1
  17. package/dist/inapp/renderPreview.d.ts.map +1 -1
  18. package/dist/inapp/renderers/active-web-chat.d.ts +7 -0
  19. package/dist/inapp/renderers/active-web-chat.d.ts.map +1 -1
  20. package/dist/inapp/renderers/card-style.d.ts +28 -0
  21. package/dist/inapp/renderers/card-style.d.ts.map +1 -0
  22. package/dist/inapp/renderers/card-template.d.ts +107 -0
  23. package/dist/inapp/renderers/card-template.d.ts.map +1 -0
  24. package/dist/inapp/renderers/carousel-cards.d.ts.map +1 -1
  25. package/dist/inapp/renderers/games.d.ts.map +1 -1
  26. package/dist/inapp/renderers/hero.d.ts +23 -0
  27. package/dist/inapp/renderers/hero.d.ts.map +1 -0
  28. package/dist/inapp/renderers/index.d.ts +4 -1
  29. package/dist/inapp/renderers/index.d.ts.map +1 -1
  30. package/dist/inapp/renderers/product-recommendation.d.ts +19 -5
  31. package/dist/inapp/renderers/product-recommendation.d.ts.map +1 -1
  32. package/dist/inapp/renderers/progress-bar.d.ts.map +1 -1
  33. package/dist/inapp/renderers/sticky-bar.d.ts.map +1 -1
  34. package/dist/inapp/renderers/stories.d.ts.map +1 -1
  35. package/dist/inapp/renderers/types.d.ts +10 -0
  36. package/dist/inapp/renderers/types.d.ts.map +1 -1
  37. package/dist/inapp/renderers/video.d.ts.map +1 -1
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1726 -391
  41. package/dist/index.js.map +1 -1
  42. package/dist/push/AegisWebPush.d.ts +1 -6
  43. package/dist/push/AegisWebPush.d.ts.map +1 -1
  44. package/dist/push/AegisWebPush.js +20 -1
  45. package/dist/push/AegisWebPush.js.map +1 -1
  46. package/dist/react.js +1 -1
  47. package/dist/runtime/AegisMessageRuntime.d.ts +11 -1
  48. package/dist/runtime/AegisMessageRuntime.d.ts.map +1 -1
  49. package/dist/triggers/IntentRuleEvaluator.d.ts +21 -0
  50. package/dist/triggers/IntentRuleEvaluator.d.ts.map +1 -1
  51. package/dist/utils/consent.d.ts.map +1 -1
  52. package/dist/widgets/AegisWidgetManager.d.ts.map +1 -1
  53. package/package.json +2 -2
  54. package/dist/analytics-DblhjFhs.mjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Storage, l as logger, A as Aegis } from "./analytics-DblhjFhs.mjs";
2
- import { B, E, N, R, U, m } from "./analytics-DblhjFhs.mjs";
1
+ import { S as Storage, l as logger, A as Aegis } from "./analytics-0p0p56H0.mjs";
2
+ import { B, E, N, R, U, m } from "./analytics-0p0p56H0.mjs";
3
3
  import { AegisWebPush } from "./push/AegisWebPush.js";
4
4
  function debounce(func, wait) {
5
5
  let timeoutId = null;
@@ -25,9 +25,700 @@ function throttle(func, limit) {
25
25
  }
26
26
  };
27
27
  }
28
+ const ICONS = {
29
+ x: [
30
+ ["path", { d: "M18 6 6 18" }],
31
+ ["path", { d: "m6 6 12 12" }]
32
+ ],
33
+ check: [["path", { d: "M20 6 9 17l-5-5" }]],
34
+ info: [
35
+ ["circle", { cx: "12", cy: "12", r: "10" }],
36
+ ["path", { d: "M12 16v-4" }],
37
+ ["path", { d: "M12 8h.01" }]
38
+ ],
39
+ "triangle-alert": [
40
+ ["path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }],
41
+ ["path", { d: "M12 9v4" }],
42
+ ["path", { d: "M12 17h.01" }]
43
+ ],
44
+ "volume-2": [
45
+ [
46
+ "path",
47
+ {
48
+ d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"
49
+ }
50
+ ],
51
+ ["path", { d: "M16 9a5 5 0 0 1 0 6" }],
52
+ ["path", { d: "M19.364 18.364a9 9 0 0 0 0-12.728" }]
53
+ ],
54
+ "volume-x": [
55
+ [
56
+ "path",
57
+ {
58
+ d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"
59
+ }
60
+ ],
61
+ ["line", { x1: "22", x2: "16", y1: "9", y2: "15" }],
62
+ ["line", { x1: "16", x2: "22", y1: "9", y2: "15" }]
63
+ ],
64
+ clock: [
65
+ ["circle", { cx: "12", cy: "12", r: "10" }],
66
+ ["polyline", { points: "12 6 12 12 16 14" }]
67
+ ],
68
+ play: [["polygon", { points: "6 3 20 12 6 21 6 3" }]],
69
+ pause: [
70
+ ["rect", { x: "14", y: "4", width: "4", height: "16", rx: "1" }],
71
+ ["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1" }]
72
+ ],
73
+ square: [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }]],
74
+ "square-check": [
75
+ ["path", { d: "M21 10.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h12.5" }],
76
+ ["path", { d: "m9 11 3 3L22 4" }]
77
+ ],
78
+ truck: [
79
+ ["path", { d: "M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2" }],
80
+ ["path", { d: "M15 18H9" }],
81
+ [
82
+ "path",
83
+ {
84
+ d: "M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"
85
+ }
86
+ ],
87
+ ["circle", { cx: "17", cy: "18", r: "2" }],
88
+ ["circle", { cx: "7", cy: "18", r: "2" }]
89
+ ],
90
+ "circle-check": [
91
+ ["circle", { cx: "12", cy: "12", r: "10" }],
92
+ ["path", { d: "m9 12 2 2 4-4" }]
93
+ ],
94
+ // ── Commerce / value-bar set (rich card editor picker) ───────────────────
95
+ "shopping-cart": [
96
+ ["circle", { cx: "8", cy: "21", r: "1" }],
97
+ ["circle", { cx: "19", cy: "21", r: "1" }],
98
+ ["path", { d: "M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12" }]
99
+ ],
100
+ "shopping-bag": [
101
+ ["path", { d: "M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z" }],
102
+ ["path", { d: "M3 6h18" }],
103
+ ["path", { d: "M16 10a4 4 0 0 1-8 0" }]
104
+ ],
105
+ gift: [
106
+ ["rect", { x: "3", y: "8", width: "18", height: "4", rx: "1" }],
107
+ ["path", { d: "M12 8v13" }],
108
+ ["path", { d: "M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7" }],
109
+ ["path", { d: "M7.5 8a2.5 2.5 0 0 1 0-5A4.8 8 0 0 1 12 8a4.8 8 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5" }]
110
+ ],
111
+ tag: [
112
+ ["path", { d: "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z" }],
113
+ ["circle", { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor" }]
114
+ ],
115
+ percent: [
116
+ ["line", { x1: "19", x2: "5", y1: "5", y2: "19" }],
117
+ ["circle", { cx: "6.5", cy: "6.5", r: "2.5" }],
118
+ ["circle", { cx: "17.5", cy: "17.5", r: "2.5" }]
119
+ ],
120
+ star: [
121
+ ["path", { d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" }]
122
+ ],
123
+ sparkles: [
124
+ ["path", { d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" }],
125
+ ["path", { d: "M20 3v4" }],
126
+ ["path", { d: "M22 5h-4" }]
127
+ ],
128
+ zap: [
129
+ ["path", { d: "M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" }]
130
+ ],
131
+ heart: [
132
+ ["path", { d: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" }]
133
+ ],
134
+ bell: [
135
+ ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0" }],
136
+ ["path", { d: "M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" }]
137
+ ],
138
+ lock: [
139
+ ["rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2" }],
140
+ ["path", { d: "M7 11V7a5 5 0 0 1 10 0v4" }]
141
+ ],
142
+ "badge-check": [
143
+ ["path", { d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" }],
144
+ ["path", { d: "m9 12 2 2 4-4" }]
145
+ ],
146
+ package: [
147
+ ["path", { d: "M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z" }],
148
+ ["path", { d: "M12 22V12" }],
149
+ ["path", { d: "m3.3 7 8.7 5 8.7-5" }],
150
+ ["path", { d: "m7.5 4.27 9 5.15" }]
151
+ ],
152
+ flame: [
153
+ ["path", { d: "M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z" }]
154
+ ],
155
+ crown: [
156
+ ["path", { d: "M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z" }],
157
+ ["path", { d: "M5 21h14" }]
158
+ ],
159
+ ticket: [
160
+ ["path", { d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z" }],
161
+ ["path", { d: "M13 5v2" }],
162
+ ["path", { d: "M13 17v2" }],
163
+ ["path", { d: "M13 11v2" }]
164
+ ],
165
+ wallet: [
166
+ ["path", { d: "M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" }],
167
+ ["path", { d: "M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" }]
168
+ ],
169
+ coins: [
170
+ ["circle", { cx: "8", cy: "8", r: "6" }],
171
+ ["path", { d: "M18.09 10.37A6 6 0 1 1 10.34 18" }],
172
+ ["path", { d: "M7 6h1v4" }],
173
+ ["path", { d: "m16.71 13.88.7.71-2.82 2.82" }]
174
+ ],
175
+ trophy: [
176
+ ["path", { d: "M6 9H4.5a2.5 2.5 0 0 1 0-5H6" }],
177
+ ["path", { d: "M18 9h1.5a2.5 2.5 0 0 0 0-5H18" }],
178
+ ["path", { d: "M4 22h16" }],
179
+ ["path", { d: "M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22" }],
180
+ ["path", { d: "M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22" }],
181
+ ["path", { d: "M18 2H6v7a6 6 0 0 0 12 0V2Z" }]
182
+ ],
183
+ timer: [
184
+ ["line", { x1: "10", x2: "14", y1: "2", y2: "2" }],
185
+ ["line", { x1: "12", x2: "15", y1: "14", y2: "11" }],
186
+ ["circle", { cx: "12", cy: "14", r: "8" }]
187
+ ],
188
+ "map-pin": [
189
+ ["path", { d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0" }],
190
+ ["circle", { cx: "12", cy: "10", r: "3" }]
191
+ ],
192
+ user: [
193
+ ["path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }],
194
+ ["circle", { cx: "12", cy: "7", r: "4" }]
195
+ ],
196
+ "thumbs-up": [
197
+ ["path", { d: "M7 10v12" }],
198
+ ["path", { d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" }]
199
+ ],
200
+ "arrow-right": [
201
+ ["path", { d: "M5 12h14" }],
202
+ ["path", { d: "m12 5 7 7-7 7" }]
203
+ ],
204
+ "chevron-right": [["path", { d: "m9 18 6-6-6-6" }]],
205
+ plus: [
206
+ ["path", { d: "M5 12h14" }],
207
+ ["path", { d: "M12 5v14" }]
208
+ ],
209
+ store: [
210
+ ["path", { d: "m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7" }],
211
+ ["path", { d: "M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" }],
212
+ ["path", { d: "M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4" }],
213
+ ["path", { d: "M2 7h20" }],
214
+ ["path", { d: "M22 7v3a2 2 0 0 1-2 2a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7" }]
215
+ ],
216
+ rocket: [
217
+ ["path", { d: "M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z" }],
218
+ ["path", { d: "m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" }],
219
+ ["path", { d: "M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" }],
220
+ ["path", { d: "M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" }]
221
+ ]
222
+ };
223
+ function escapeAttr(value) {
224
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
225
+ }
226
+ const SVG_NS = "http://www.w3.org/2000/svg";
227
+ function lucideSvgEl(name, opts = {}) {
228
+ const node = ICONS[name];
229
+ if (!node || typeof document === "undefined") return null;
230
+ const size = opts.size ?? 24;
231
+ const svg = document.createElementNS(SVG_NS, "svg");
232
+ svg.setAttribute("width", String(size));
233
+ svg.setAttribute("height", String(size));
234
+ svg.setAttribute("viewBox", "0 0 24 24");
235
+ svg.setAttribute("fill", "none");
236
+ svg.setAttribute("stroke", opts.color ?? "currentColor");
237
+ svg.setAttribute("stroke-width", String(opts.strokeWidth ?? 2));
238
+ svg.setAttribute("stroke-linecap", "round");
239
+ svg.setAttribute("stroke-linejoin", "round");
240
+ if (opts.className) svg.setAttribute("class", opts.className);
241
+ if (opts.ariaLabel) {
242
+ svg.setAttribute("role", "img");
243
+ svg.setAttribute("aria-label", opts.ariaLabel);
244
+ } else {
245
+ svg.setAttribute("aria-hidden", "true");
246
+ }
247
+ for (const [tag, attrs] of node) {
248
+ const child = document.createElementNS(SVG_NS, tag);
249
+ for (const [k, v] of Object.entries(attrs)) child.setAttribute(k, String(v));
250
+ svg.appendChild(child);
251
+ }
252
+ return svg;
253
+ }
254
+ function lucideSvg(name, opts = {}) {
255
+ const node = ICONS[name];
256
+ if (!node) return "";
257
+ const size = opts.size ?? 24;
258
+ const color = opts.color ?? "currentColor";
259
+ const strokeWidth = opts.strokeWidth ?? 2;
260
+ const a11y = opts.ariaLabel ? `role="img" aria-label="${escapeAttr(opts.ariaLabel)}"` : 'aria-hidden="true"';
261
+ const cls = opts.className ? ` class="${escapeAttr(opts.className)}"` : "";
262
+ const children = node.map(([tag, attrs]) => {
263
+ const attrStr = Object.entries(attrs).map(([k, v]) => `${k}="${escapeAttr(String(v))}"`).join(" ");
264
+ return `<${tag} ${attrStr}/>`;
265
+ }).join("");
266
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${escapeAttr(color)}" stroke-width="${strokeWidth}" stroke-linecap="round" stroke-linejoin="round"${cls} ${a11y}>${children}</svg>`;
267
+ }
268
+ const WEIGHT = {
269
+ regular: "400",
270
+ medium: "500",
271
+ semibold: "600",
272
+ bold: "700"
273
+ };
274
+ const ICON_PX = { sm: 22, md: 30, lg: 40 };
275
+ const TEXT_PX = { sm: 11.5, md: 13, lg: 15 };
276
+ function interpolateTokens(s, ctx) {
277
+ if (!s || s.indexOf("{") === -1) return s;
278
+ const props = ctx.properties && ctx.properties() || {};
279
+ const cart = ctx.getCartState && ctx.getCartState();
280
+ return s.replace(/\{([a-z0-9_.]+)\}/gi, (_m, key) => {
281
+ let v = props[key];
282
+ if (v == null) {
283
+ if (key === "cart.total") v = cart == null ? void 0 : cart.total;
284
+ else if (key === "cart.count") v = cart == null ? void 0 : cart.itemCount;
285
+ }
286
+ return v == null || v === "" ? "" : String(v);
287
+ });
288
+ }
289
+ const ASPECT$2 = { "1/1": "1 / 1", "4/3": "4 / 3", "16/9": "16 / 9" };
290
+ const DEFAULT_CARD_TEMPLATE = {
291
+ id: "clean",
292
+ frame: { bg: "#ffffff", border: "#f1f1f3", radius: 14 },
293
+ elements: [
294
+ { type: "image", aspect: "1/1", radius: 10 },
295
+ { type: "price", variant: "pill", color: "#059669" },
296
+ { type: "title", lines: 2 },
297
+ { type: "rating" },
298
+ // Zepto/Blinkit-style ADD: a single OUTLINED pill (white fill, brand
299
+ // border + brand text) overlaid on the bottom-right of the product image —
300
+ // NOT a solid filled bar. `overlay:true` anchors it onto the image; the
301
+ // outline variant paints the white/border look. The rail's tile-body click
302
+ // still opens the product, so this one CTA owns add-to-cart.
303
+ { type: "cta", variant: "outline", overlay: true }
304
+ ]
305
+ };
306
+ function asLook(v) {
307
+ return v && typeof v === "object" ? v : {};
308
+ }
309
+ function templateFromCardStyle(look) {
310
+ const elements = [{ type: "image", aspect: "1/1", radius: 10 }];
311
+ if (look.badge) elements.push({ type: "badge", text: look.badge, color: look.badge_color });
312
+ if (!look.hide_price) elements.push({ type: "price", variant: "pill", color: "#059669" });
313
+ elements.push({ type: "title", lines: 2 });
314
+ if (!look.hide_rating) elements.push({ type: "rating" });
315
+ if (!look.hide_cta) elements.push({ type: "cta", variant: "fill", text: look.cta_text });
316
+ return {
317
+ id: "legacy",
318
+ frame: {
319
+ bg: look.background,
320
+ border: look.border_color,
321
+ radius: look.radius,
322
+ accent: look.accent ?? null
323
+ },
324
+ elements
325
+ };
326
+ }
327
+ function resolveGlobalTemplate(ic) {
328
+ const t = ic.card_template;
329
+ if (t && typeof t === "object" && Array.isArray(t.elements)) {
330
+ return t;
331
+ }
332
+ const legacy = ic.card_style;
333
+ if (legacy && typeof legacy === "object") {
334
+ return templateFromCardStyle(asLook(legacy));
335
+ }
336
+ return DEFAULT_CARD_TEMPLATE;
337
+ }
338
+ function applyPerCardOverride(tpl, look) {
339
+ if (!look || Object.keys(look).length === 0) return tpl;
340
+ const frame = { ...tpl.frame || {} };
341
+ if (look.accent) frame.accent = look.accent;
342
+ if (look.radius !== void 0) frame.radius = look.radius;
343
+ let elements = (tpl.elements || []).slice();
344
+ const drop = (type) => elements = elements.filter((e) => e.type !== type);
345
+ if (look.hide_price) drop("price");
346
+ if (look.hide_cta) drop("cta");
347
+ if (look.hide_rating) drop("rating");
348
+ if (look.cta_text) elements = elements.map((e) => e.type === "cta" ? { ...e, text: look.cta_text } : e);
349
+ if (look.badge) {
350
+ const badgeEl = { type: "badge", text: look.badge, color: look.badge_color };
351
+ elements = elements.some((e) => e.type === "badge") ? elements.map((e) => e.type === "badge" ? badgeEl : e) : [elements[0], badgeEl, ...elements.slice(1)].filter(Boolean);
352
+ }
353
+ return { ...tpl, frame, elements };
354
+ }
355
+ function priceText(card) {
356
+ const meta = card.metadata || {};
357
+ const p = meta.price;
358
+ if (p !== void 0 && p !== null) return String(p);
359
+ if (card.body && /\d/.test(card.body)) return card.body;
360
+ return void 0;
361
+ }
362
+ function paintPrice(card, el, accent, ctx) {
363
+ const price = priceText(card);
364
+ if (price === void 0) return null;
365
+ const meta = card.metadata || {};
366
+ const compareAt = meta.compare_at_price ?? meta.compare_at ?? meta.original_price;
367
+ const row = document.createElement("div");
368
+ row.style.cssText = "display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;";
369
+ const p = document.createElement("span");
370
+ p.textContent = price;
371
+ if (el.variant === "plain") {
372
+ p.style.cssText = `color:${accent};font-weight:700;font-size:13px;`;
373
+ } else {
374
+ const pillBg = el.color ? ctx.sanitizeColor(el.color) : "#059669";
375
+ p.style.cssText = `background:${pillBg};color:#fff;font-weight:800;font-size:12px;padding:2px 7px;border-radius:6px;`;
376
+ }
377
+ row.appendChild(p);
378
+ const pNum = parseFloat(price.replace(/[^0-9.]/g, ""));
379
+ const cNum = compareAt !== void 0 ? parseFloat(String(compareAt).replace(/[^0-9.]/g, "")) : NaN;
380
+ if (isFinite(cNum) && isFinite(pNum) && cNum > pNum) {
381
+ const orig = document.createElement("span");
382
+ orig.textContent = String(compareAt);
383
+ orig.style.cssText = "text-decoration:line-through;opacity:0.5;font-size:11px;";
384
+ row.appendChild(orig);
385
+ const off = document.createElement("span");
386
+ off.textContent = `-${Math.round((1 - pNum / cNum) * 100)}%`;
387
+ off.style.cssText = "background:#ef444422;color:#ef4444;font-weight:700;font-size:10px;padding:1px 5px;border-radius:999px;";
388
+ row.appendChild(off);
389
+ }
390
+ return row;
391
+ }
392
+ function paintRating(card) {
393
+ const meta = card.metadata || {};
394
+ const avg = Number(meta.avg_rating ?? 0);
395
+ if (!(avg > 0)) return null;
396
+ const count = Number(meta.review_count ?? 0);
397
+ const rr = document.createElement("div");
398
+ rr.style.cssText = "display:flex;align-items:center;gap:3px;font-size:11px;line-height:1;";
399
+ const star = document.createElement("span");
400
+ star.textContent = "★";
401
+ star.style.cssText = "color:#059669;";
402
+ const val = document.createElement("span");
403
+ val.textContent = avg.toFixed(1);
404
+ val.style.cssText = "color:#059669;font-weight:700;";
405
+ rr.appendChild(star);
406
+ rr.appendChild(val);
407
+ if (count > 0) {
408
+ const c = document.createElement("span");
409
+ c.textContent = `(${count})`;
410
+ c.style.cssText = "color:#94a3b8;";
411
+ rr.appendChild(c);
412
+ }
413
+ return rr;
414
+ }
415
+ function contrastFg(bg) {
416
+ const raw = bg.replace("#", "");
417
+ if (raw.length < 3) return "#1f2937";
418
+ const h = raw.length === 3 ? raw.split("").map((c) => c + c).join("") : raw.slice(0, 6);
419
+ const r = parseInt(h.slice(0, 2), 16);
420
+ const g = parseInt(h.slice(2, 4), 16);
421
+ const b = parseInt(h.slice(4, 6), 16);
422
+ if ([r, g, b].some((n) => Number.isNaN(n))) return "#1f2937";
423
+ const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
424
+ return lum < 0.5 ? "#ffffff" : "#1f2937";
425
+ }
426
+ function buildProgressElement(el, ctx, accent, muted) {
427
+ const { trackEvent, campaign, sanitizeColor } = ctx;
428
+ const goal = el.goal_type || "cart_total";
429
+ const moneyGoal = goal === "cart_total";
430
+ const fill = el.fill ? sanitizeColor(el.fill) : accent;
431
+ const props = () => ctx.properties && ctx.properties() || {};
432
+ const sym = () => {
433
+ var _a;
434
+ if (el.currency_symbol) return el.currency_symbol;
435
+ const code = ctx.getCartState && ((_a = ctx.getCartState()) == null ? void 0 : _a.currency);
436
+ const M = { INR: "₹", USD: "$", EUR: "€", GBP: "£" };
437
+ return code && M[code] || "";
438
+ };
439
+ const threshold = () => {
440
+ if (typeof el.threshold === "number" && el.threshold > 0) return el.threshold;
441
+ if (el.threshold_source === "free_delivery_setting") {
442
+ const v = Number(props()["cart.free_delivery_above"]);
443
+ if (v > 0) return v;
444
+ }
445
+ return 0;
446
+ };
447
+ const readCur = () => {
448
+ const cart = ctx.getCartState && ctx.getCartState();
449
+ if (goal === "items_in_cart") return Number(cart == null ? void 0 : cart.itemCount) || 0;
450
+ if (goal === "cart_total") return Number(cart == null ? void 0 : cart.total) || 0;
451
+ const p = props();
452
+ if (goal === "loyalty_points") return Number(p["loyalty.points"]) || 0;
453
+ if (goal === "referrals") return Number(p["referrals.count"]) || 0;
454
+ return 0;
455
+ };
456
+ const wrap = document.createElement("div");
457
+ wrap.style.cssText = "display:flex;flex-direction:column;gap:3px;min-width:0;width:100%;";
458
+ const sub = document.createElement("div");
459
+ sub.style.cssText = `font-size:11.5px;line-height:1.2;color:${muted};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`;
460
+ const shell = document.createElement("div");
461
+ shell.style.cssText = `height:3px;border-radius:999px;background:color-mix(in srgb, ${fill} 16%, transparent);overflow:hidden;`;
462
+ const bar = document.createElement("div");
463
+ bar.style.cssText = `height:100%;border-radius:999px;background:${fill};width:0%;transition:width .4s cubic-bezier(.4,0,.2,1);`;
464
+ shell.appendChild(bar);
465
+ wrap.appendChild(sub);
466
+ wrap.appendChild(shell);
467
+ let firedUnlock = false;
468
+ const update = () => {
469
+ const th = threshold();
470
+ const cur = readCur();
471
+ const pct = th > 0 ? Math.max(0, Math.min(100, cur / th * 100)) : 0;
472
+ bar.style.width = `${pct}%`;
473
+ if (th > 0 && pct >= 100) {
474
+ sub.textContent = el.reward_text || "Unlocked!";
475
+ sub.style.color = "#34d399";
476
+ if (!firedUnlock) {
477
+ firedUnlock = true;
478
+ trackEvent(campaign.id, "clicked", { stepId: el.id || "unlocked" });
479
+ }
480
+ } else {
481
+ sub.style.color = muted;
482
+ const remaining = Math.max(0, th - cur);
483
+ const pre = moneyGoal ? sym() : "";
484
+ const pctStr = el.show_pct && th > 0 ? ` · ${Math.round(pct)}%` : "";
485
+ sub.textContent = th > 0 ? `Shop for ${pre}${remaining.toFixed(0)} more${pctStr}` : el.reward_text || "";
486
+ }
487
+ };
488
+ update();
489
+ const timer = setInterval(update, 1e3);
490
+ if (typeof window !== "undefined") {
491
+ window.addEventListener("beforeunload", () => clearInterval(timer), { once: true });
492
+ }
493
+ return wrap;
494
+ }
495
+ function renderCardFromTemplate(card, opts) {
496
+ const { ctx, action, layout, layoutAccent } = opts;
497
+ const inline = action === "cart";
498
+ const { campaign, trackEvent, sanitizeUrl, sanitizeColor } = ctx;
499
+ const tpl = applyPerCardOverride(opts.template, asLook(card.style));
500
+ const frame = tpl.frame || {};
501
+ const meta = card.metadata || {};
502
+ const accent = typeof frame.accent === "string" && frame.accent ? sanitizeColor(frame.accent) : layoutAccent;
503
+ const brand = `var(--aegis-brand-accent, ${accent})`;
504
+ const fg = contrastFg(frame.bg ? sanitizeColor(frame.bg) : "#ffffff");
505
+ const muted = fg === "#ffffff" ? "rgba(255,255,255,0.72)" : "#6b7280";
506
+ const tile = document.createElement("div");
507
+ const radius = frame.radius !== void 0 ? `${parseInt(String(frame.radius), 10) || 12}px` : "12px";
508
+ const fLayout = frame.layout;
509
+ const dir = fLayout === "row" ? "row" : "column";
510
+ const tileGap = frame.gap != null ? frame.gap : 6;
511
+ const sizing = fLayout ? frame.width != null ? `width:${typeof frame.width === "number" ? `${frame.width}px` : frame.width};` : "flex:1 1 auto; width:100%;" : layout === "grid" ? "" : "flex:0 0 150px; scroll-snap-align:start;";
512
+ tile.style.cssText = `
513
+ box-sizing:border-box; display:flex; flex-direction:${dir}; ${fLayout === "row" ? "align-items:center;" : ""} gap:${tileGap}px; position:relative;
514
+ color:${fg};
515
+ background:${frame.bg ? sanitizeColor(frame.bg) : "#ffffff"};
516
+ border:1px solid ${frame.border ? sanitizeColor(frame.border) : "transparent"};
517
+ border-radius:${radius}; padding:8px;
518
+ ${frame.max_width ? `max-width:${frame.max_width}px;` : ""}${frame.min_width ? `min-width:${frame.min_width}px;` : ""}
519
+ ${sizing}
520
+ cursor:${card.cta_url || inline ? "pointer" : "default"}; transition:transform 0.15s ease;
521
+ `;
522
+ const elements = tpl.elements && tpl.elements.length ? tpl.elements : DEFAULT_CARD_TEMPLATE.elements;
523
+ let imageWrap = null;
524
+ const paintInto = (container, els) => {
525
+ for (const el of els) {
526
+ switch (el.type) {
527
+ case "image": {
528
+ const wrap = document.createElement("div");
529
+ wrap.style.cssText = `position:relative;width:100%;aspect-ratio:${ASPECT$2[el.aspect || "1/1"] || "1 / 1"};border-radius:${el.radius ?? 10}px;overflow:hidden;background:#f7f8fa;`;
530
+ const safeVideo = card.video_url ? sanitizeUrl(card.video_url) : "";
531
+ if (safeVideo) {
532
+ const v = document.createElement("video");
533
+ v.src = safeVideo;
534
+ v.muted = true;
535
+ v.loop = true;
536
+ v.autoplay = true;
537
+ v.playsInline = true;
538
+ v.setAttribute("playsinline", "");
539
+ v.style.cssText = "width:100%;height:100%;object-fit:cover;display:block;";
540
+ wrap.appendChild(v);
541
+ } else if (card.image_url) {
542
+ const safe = sanitizeUrl(card.image_url);
543
+ if (safe) {
544
+ const img = document.createElement("img");
545
+ img.src = safe;
546
+ img.alt = "";
547
+ img.loading = "lazy";
548
+ img.style.cssText = "width:100%;height:100%;object-fit:cover;display:block;";
549
+ wrap.appendChild(img);
550
+ }
551
+ }
552
+ imageWrap = wrap;
553
+ container.appendChild(wrap);
554
+ break;
555
+ }
556
+ case "badge": {
557
+ const text = el.text;
558
+ if (!text) break;
559
+ const badge = document.createElement("span");
560
+ badge.textContent = text;
561
+ const bg = el.color ? sanitizeColor(el.color) : brand;
562
+ badge.style.cssText = `position:absolute;top:6px;left:6px;z-index:1;background:${bg};color:#fff;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:0.03em;padding:3px 6px;border-radius:5px;`;
563
+ (imageWrap || container).appendChild(badge);
564
+ break;
565
+ }
566
+ case "price": {
567
+ const node = paintPrice(card, el, accent, ctx);
568
+ if (node) container.appendChild(node);
569
+ break;
570
+ }
571
+ case "title": {
572
+ if (!card.title) break;
573
+ const t = document.createElement("div");
574
+ t.textContent = card.title;
575
+ const lines = el.lines && el.lines > 0 ? el.lines : 2;
576
+ t.style.cssText = `font-size:12.5px;font-weight:600;line-height:1.3;text-align:${el.align ?? "left"};color:${el.color ? sanitizeColor(el.color) : fg};display:-webkit-box;-webkit-line-clamp:${lines};-webkit-box-orient:vertical;overflow:hidden;`;
577
+ container.appendChild(t);
578
+ break;
579
+ }
580
+ case "subtitle": {
581
+ if (!card.body) break;
582
+ const s = document.createElement("div");
583
+ s.textContent = card.body;
584
+ const lines = el.lines && el.lines > 0 ? el.lines : 2;
585
+ s.style.cssText = `font-size:11px;color:${el.color ? sanitizeColor(el.color) : muted};line-height:1.3;text-align:${el.align ?? "left"};display:-webkit-box;-webkit-line-clamp:${lines};-webkit-box-orient:vertical;overflow:hidden;`;
586
+ container.appendChild(s);
587
+ break;
588
+ }
589
+ case "rating": {
590
+ const node = paintRating(card);
591
+ if (node) container.appendChild(node);
592
+ break;
593
+ }
594
+ case "category": {
595
+ const cat = meta.category;
596
+ if (typeof cat !== "string" || !cat) break;
597
+ const chip = document.createElement("span");
598
+ chip.textContent = cat;
599
+ chip.style.cssText = "align-self:flex-start;background:#f3f4f6;color:#6b7280;font-size:10px;font-weight:500;padding:2px 7px;border-radius:6px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";
600
+ container.appendChild(chip);
601
+ break;
602
+ }
603
+ // ── rich-card-editor elements (P0) ──────────────────────────────────
604
+ case "icon": {
605
+ const px = ICON_PX[el.size || "md"] || 30;
606
+ const node = document.createElement("div");
607
+ const iconColor = el.color ? sanitizeColor(el.color) : fg;
608
+ if (el.bubble) {
609
+ const bubbleTint = el.color ? sanitizeColor(el.color) : brand;
610
+ node.style.cssText = `flex:0 0 auto;width:${px}px;height:${px}px;border-radius:999px;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb, ${bubbleTint} 16%, transparent);`;
611
+ } else {
612
+ node.style.cssText = "flex:0 0 auto;display:flex;align-items:center;";
613
+ }
614
+ const svg = lucideSvgEl(el.icon_name || "circle-check", {
615
+ size: el.bubble ? Math.round(px * 0.55) : Math.round(px * 0.7),
616
+ color: iconColor,
617
+ strokeWidth: 2
618
+ });
619
+ if (svg) node.appendChild(svg);
620
+ container.appendChild(node);
621
+ break;
622
+ }
623
+ case "text": {
624
+ const t = document.createElement("div");
625
+ const fs = TEXT_PX[el.size || "md"] || 13;
626
+ t.style.cssText = `font-size:${fs}px;font-weight:${WEIGHT[el.weight ?? "medium"]};text-align:${el.align ?? "left"};color:${el.color ? sanitizeColor(el.color) : fg};line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;`;
627
+ t.textContent = interpolateTokens(el.content ?? "", ctx);
628
+ container.appendChild(t);
629
+ break;
630
+ }
631
+ case "spacer": {
632
+ const sp = document.createElement("div");
633
+ sp.style.height = `${el.height ?? 8}px`;
634
+ container.appendChild(sp);
635
+ break;
636
+ }
637
+ case "progress": {
638
+ container.appendChild(buildProgressElement(el, ctx, brand, muted));
639
+ break;
640
+ }
641
+ case "group": {
642
+ const g = document.createElement("div");
643
+ const gdir = el.layout === "row" ? "row" : "column";
644
+ g.style.cssText = `display:flex;flex-direction:${gdir};${el.layout === "row" ? "align-items:center;" : ""}gap:${el.gap ?? 4}px;min-width:0;${el.layout === "row" ? "" : "flex:1 1 auto;"}`;
645
+ paintInto(g, el.elements || []);
646
+ container.appendChild(g);
647
+ break;
648
+ }
649
+ case "cta": {
650
+ const label = el.text || card.cta_text || (inline ? "Add" : "Shop");
651
+ const cta = document.createElement("button");
652
+ cta.className = "aegis-cta";
653
+ cta.textContent = label;
654
+ const overlayOnImage = !!el.overlay && !!imageWrap;
655
+ if (overlayOnImage) {
656
+ cta.style.cssText = el.variant === "outline" ? `position:absolute;right:6px;bottom:6px;background:#fff;color:${brand};border:1px solid ${brand};border-radius:9px;padding:5px 18px;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:0.04em;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.14);` : `position:absolute;right:6px;bottom:6px;background:${brand};color:#fff;border:none;border-radius:9px;padding:5px 18px;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:0.04em;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.14);`;
657
+ } else {
658
+ cta.style.cssText = el.variant === "outline" ? `margin-top:${fLayout === "row" ? "0" : "auto"};background:transparent;color:${brand};border:1px solid ${brand};border-radius:999px;padding:6px 10px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;` : `margin-top:${fLayout === "row" ? "0" : "auto"};background:${brand};color:#fff;border:none;border-radius:999px;padding:7px 10px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;`;
659
+ }
660
+ cta.addEventListener("click", (e) => {
661
+ e.stopPropagation();
662
+ trackEvent(campaign.id, "clicked", { stepId: el.id || "add" });
663
+ if (inline) {
664
+ if (typeof window !== "undefined") {
665
+ window.dispatchEvent(
666
+ new CustomEvent("aegis:add-to-cart", {
667
+ detail: {
668
+ campaign_id: campaign.id,
669
+ product_id: meta.product_id,
670
+ sku: meta.sku,
671
+ title: card.title,
672
+ price: meta.price ?? card.body,
673
+ image_url: card.image_url,
674
+ cta_url: card.cta_url
675
+ }
676
+ })
677
+ );
678
+ }
679
+ return;
680
+ }
681
+ if (card.cta_url) {
682
+ const safe = sanitizeUrl(card.cta_url);
683
+ if (safe) ctx.navigate ? ctx.navigate(safe) : window.location.href = safe;
684
+ }
685
+ });
686
+ (overlayOnImage ? imageWrap : container).appendChild(cta);
687
+ break;
688
+ }
689
+ }
690
+ }
691
+ };
692
+ paintInto(tile, elements);
693
+ const open = (e) => {
694
+ e.stopPropagation();
695
+ trackEvent(campaign.id, "clicked");
696
+ if (inline) {
697
+ if (typeof window !== "undefined") {
698
+ window.dispatchEvent(
699
+ new CustomEvent("aegis:open-product", {
700
+ detail: {
701
+ campaign_id: campaign.id,
702
+ product_id: meta.product_id,
703
+ sku: meta.sku,
704
+ cta_url: card.cta_url
705
+ }
706
+ })
707
+ );
708
+ }
709
+ return;
710
+ }
711
+ if (card.cta_url) {
712
+ const safe = sanitizeUrl(card.cta_url);
713
+ if (safe) ctx.navigate ? ctx.navigate(safe) : window.location.href = safe;
714
+ }
715
+ };
716
+ if (inline || card.cta_url) tile.addEventListener("click", open);
717
+ return tile;
718
+ }
28
719
  const MAX_CARDS_RENDERED = 10;
29
720
  function renderCarouselCards(ctx) {
30
- const { campaign, trackEvent, sanitizeUrl, sanitizeColor, log, addAnimationStyles } = ctx;
721
+ const { campaign, trackEvent, sanitizeColor, log, addAnimationStyles } = ctx;
31
722
  const ic = campaign.interactive_config || {};
32
723
  const rawCards = Array.isArray(ic.cards) ? ic.cards : [];
33
724
  const cards = rawCards.slice(0, MAX_CARDS_RENDERED);
@@ -70,7 +761,7 @@ function renderCarouselCards(ctx) {
70
761
  headerText.appendChild(body);
71
762
  header.appendChild(headerText);
72
763
  const closeBtn = document.createElement("button");
73
- closeBtn.textContent = "";
764
+ closeBtn.innerHTML = lucideSvg("x", { size: 16 });
74
765
  closeBtn.setAttribute("aria-label", "Close");
75
766
  closeBtn.style.cssText = `
76
767
  background: transparent; border: none; color: inherit;
@@ -89,68 +780,16 @@ function renderCarouselCards(ctx) {
89
780
  padding-bottom: 4px; -webkit-overflow-scrolling: touch;
90
781
  `;
91
782
  track.style.msOverflowStyle = "none";
783
+ const template = resolveGlobalTemplate(ic);
92
784
  cards.forEach((c, i) => {
93
- const tile = document.createElement("div");
785
+ const tile = renderCardFromTemplate(c, {
786
+ template,
787
+ ctx,
788
+ action: "navigate",
789
+ layout: "row",
790
+ layoutAccent: accent
791
+ });
94
792
  tile.setAttribute("data-card-index", String(i));
95
- tile.style.cssText = `
96
- flex: 0 0 auto; width: 140px; scroll-snap-align: start;
97
- background: ${fg}0a; border-radius: 12px; padding: 10px;
98
- display: flex; flex-direction: column; gap: 6px;
99
- cursor: ${c.cta_url ? "pointer" : "default"};
100
- `;
101
- const safeVideo = c.video_url ? sanitizeUrl(c.video_url) : "";
102
- if (safeVideo) {
103
- const vid = document.createElement("video");
104
- vid.src = safeVideo;
105
- vid.muted = true;
106
- vid.loop = true;
107
- vid.autoplay = true;
108
- vid.playsInline = true;
109
- vid.setAttribute("playsinline", "");
110
- vid.style.cssText = "width: 100%; height: 96px; border-radius: 8px; object-fit: cover;";
111
- tile.appendChild(vid);
112
- } else if (c.image_url) {
113
- const img = document.createElement("img");
114
- const safe = sanitizeUrl(c.image_url);
115
- if (safe) {
116
- img.src = safe;
117
- img.alt = "";
118
- img.loading = "lazy";
119
- img.style.cssText = "width: 100%; height: 96px; border-radius: 8px; object-fit: cover;";
120
- tile.appendChild(img);
121
- }
122
- }
123
- if (c.title) {
124
- const t = document.createElement("div");
125
- t.textContent = c.title;
126
- t.style.cssText = "font-weight: 600; font-size: 13px; line-height: 1.3;";
127
- tile.appendChild(t);
128
- }
129
- if (c.body) {
130
- const b = document.createElement("div");
131
- b.textContent = c.body;
132
- b.style.cssText = "font-size: 11.5px; opacity: 0.72; line-height: 1.3;";
133
- tile.appendChild(b);
134
- }
135
- if (c.cta_text && c.cta_url) {
136
- const cta = document.createElement("button");
137
- cta.className = "aegis-cta";
138
- cta.textContent = c.cta_text;
139
- cta.style.cssText = `
140
- margin-top: auto; background: ${accent}; color: #fff;
141
- border: none; padding: 6px 10px; border-radius: 999px;
142
- font-size: 12px; font-weight: 600; cursor: pointer;
143
- `;
144
- const goto2 = (e) => {
145
- e.stopPropagation();
146
- trackEvent(campaign.id, "clicked", { stepId: `card_${i}` });
147
- const safe = sanitizeUrl(c.cta_url);
148
- if (safe) window.location.href = safe;
149
- };
150
- cta.addEventListener("click", goto2);
151
- tile.appendChild(cta);
152
- tile.addEventListener("click", goto2);
153
- }
154
793
  track.appendChild(tile);
155
794
  });
156
795
  card.appendChild(track);
@@ -314,7 +953,7 @@ function renderStickyBar(ctx) {
314
953
  };
315
954
  if (dismissible) {
316
955
  const close = document.createElement("button");
317
- close.textContent = "";
956
+ close.innerHTML = lucideSvg("x", { size: 16 });
318
957
  close.setAttribute("aria-label", "Dismiss");
319
958
  close.style.cssText = `
320
959
  background: transparent; border: none; color: inherit;
@@ -344,13 +983,12 @@ function buildProgressBar(ctx, inline) {
344
983
  const source = ic.progress_source === "sse" ? "sse" : "client";
345
984
  addAnimationStyles();
346
985
  const brand = sanitizeColor(campaign.background_color || "#4169e1");
986
+ const accentVar = `var(--aegis-brand-accent, ${brand})`;
347
987
  const branded = ic.surface_style === "branded";
348
- const bg = branded ? brand : "#ffffff";
988
+ const bg = branded ? accentVar : "#ffffff";
349
989
  const fg = sanitizeColor(campaign.text_color || (branded ? "#ffffff" : "#0f172a"));
350
- const fill = sanitizeColor(
351
- ic.progress_fill_color || (branded ? "#ffffff" : brand)
352
- );
353
- const track = branded ? "rgba(255,255,255,0.25)" : `${fill}22`;
990
+ const fill = ic.progress_fill_color ? sanitizeColor(ic.progress_fill_color) : branded ? "#ffffff" : accentVar;
991
+ const track = branded ? "rgba(255,255,255,0.25)" : `color-mix(in srgb, ${fill} 14%, transparent)`;
354
992
  const bar = document.createElement("div");
355
993
  bar.className = "aegis-in-app-progress-bar";
356
994
  bar.setAttribute("data-campaign-id", campaign.id);
@@ -730,7 +1368,7 @@ function renderCoachmarkTour(ctx) {
730
1368
  }
731
1369
  const MAX_PRODUCTS = 24;
732
1370
  function renderProductRecommendation(ctx) {
733
- const { campaign, trackEvent, sanitizeUrl, sanitizeColor, log, addAnimationStyles } = ctx;
1371
+ const { campaign, trackEvent, sanitizeColor, log, addAnimationStyles } = ctx;
734
1372
  const ic = campaign.interactive_config || {};
735
1373
  const rawCards = Array.isArray(ic.cards) ? ic.cards : [];
736
1374
  const cards = rawCards.slice(0, MAX_PRODUCTS);
@@ -739,7 +1377,7 @@ function renderProductRecommendation(ctx) {
739
1377
  return;
740
1378
  }
741
1379
  const layout = ic.rec_layout || "grid";
742
- const ctaDefault = ic.rec_cta_text || "Shop now";
1380
+ const template = resolveGlobalTemplate(ic);
743
1381
  addAnimationStyles();
744
1382
  const bg = "#ffffff";
745
1383
  const fg = "#0f172a";
@@ -782,7 +1420,7 @@ function renderProductRecommendation(ctx) {
782
1420
  headerText.appendChild(body);
783
1421
  header.appendChild(headerText);
784
1422
  const close = document.createElement("button");
785
- close.textContent = "";
1423
+ close.innerHTML = lucideSvg("x", { size: 18 });
786
1424
  close.setAttribute("aria-label", "Close");
787
1425
  close.style.cssText = `
788
1426
  background: transparent; border: none; color: inherit;
@@ -807,82 +1445,9 @@ function renderProductRecommendation(ctx) {
807
1445
  `;
808
1446
  }
809
1447
  cards.forEach((c) => {
810
- const tile = document.createElement("div");
811
- const isRow = layout === "row";
812
- tile.style.cssText = `
813
- background: ${fg}08; border-radius: 12px;
814
- padding: 10px; display: flex; flex-direction: column; gap: 6px;
815
- cursor: ${c.cta_url ? "pointer" : "default"};
816
- ${isRow ? "flex: 0 0 150px; scroll-snap-align: start;" : ""}
817
- transition: transform 0.15s ease;
818
- `;
819
- if (c.image_url) {
820
- const img = document.createElement("img");
821
- const safe = sanitizeUrl(c.image_url);
822
- if (safe) {
823
- img.src = safe;
824
- img.alt = "";
825
- img.loading = "lazy";
826
- img.style.cssText = "width: 100%; aspect-ratio: 1 / 1; border-radius: 8px; object-fit: cover;";
827
- tile.appendChild(img);
828
- }
829
- }
830
- if (c.title) {
831
- const t = document.createElement("div");
832
- t.textContent = c.title;
833
- t.style.cssText = "font-weight: 600; font-size: 13px; line-height: 1.3;";
834
- tile.appendChild(t);
835
- }
836
- const meta = c.metadata && typeof c.metadata === "object" ? c.metadata : {};
837
- const price = meta.price;
838
- const compareAt = meta.compare_at_price ?? meta.compare_at ?? meta.original_price;
839
- if (price !== void 0) {
840
- const priceRow = document.createElement("div");
841
- priceRow.style.cssText = "display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;";
842
- const p = document.createElement("span");
843
- p.textContent = String(price);
844
- p.style.cssText = `color: ${accent}; font-weight: 700; font-size: 13px;`;
845
- priceRow.appendChild(p);
846
- const pNum = parseFloat(String(price).replace(/[^0-9.]/g, ""));
847
- const cNum = compareAt !== void 0 ? parseFloat(String(compareAt).replace(/[^0-9.]/g, "")) : NaN;
848
- if (isFinite(cNum) && isFinite(pNum) && cNum > pNum) {
849
- const orig = document.createElement("span");
850
- orig.textContent = String(compareAt);
851
- orig.style.cssText = "text-decoration: line-through; opacity: 0.5; font-size: 11px;";
852
- priceRow.appendChild(orig);
853
- const off = document.createElement("span");
854
- off.textContent = `-${Math.round((1 - pNum / cNum) * 100)}%`;
855
- off.style.cssText = "background: #ef444422; color: #ef4444; font-weight: 700; font-size: 10px; padding: 1px 5px; border-radius: 999px;";
856
- priceRow.appendChild(off);
857
- }
858
- tile.appendChild(priceRow);
859
- } else if (c.body) {
860
- const b = document.createElement("div");
861
- b.textContent = c.body;
862
- b.style.cssText = "font-size: 11.5px; opacity: 0.72; line-height: 1.3;";
863
- tile.appendChild(b);
864
- }
865
- const cta = document.createElement("button");
866
- cta.className = "aegis-cta";
867
- cta.textContent = c.cta_text || ctaDefault;
868
- cta.style.cssText = `
869
- margin-top: auto;
870
- background: ${accent}; color: #fff;
871
- border: none; padding: 7px 10px; border-radius: 999px;
872
- font-size: 12px; font-weight: 600; cursor: pointer;
873
- `;
874
- const go = (e) => {
875
- e.stopPropagation();
876
- trackEvent(campaign.id, "clicked");
877
- if (c.cta_url) {
878
- const safe = sanitizeUrl(c.cta_url);
879
- if (safe) window.location.href = safe;
880
- }
881
- };
882
- cta.addEventListener("click", go);
883
- tile.appendChild(cta);
884
- if (c.cta_url) tile.addEventListener("click", go);
885
- grid.appendChild(tile);
1448
+ grid.appendChild(
1449
+ renderCardFromTemplate(c, { template, ctx, action: "navigate", layout, layoutAccent: accent })
1450
+ );
886
1451
  });
887
1452
  sheet.appendChild(grid);
888
1453
  overlay.appendChild(sheet);
@@ -894,6 +1459,88 @@ function renderProductRecommendation(ctx) {
894
1459
  });
895
1460
  document.body.appendChild(overlay);
896
1461
  }
1462
+ function renderProductRecommendationInline(ctx, target) {
1463
+ const { campaign, sanitizeColor, addAnimationStyles, log } = ctx;
1464
+ const ic = campaign.interactive_config || {};
1465
+ const rawCards = Array.isArray(ic.cards) ? ic.cards : [];
1466
+ const cards = rawCards.slice(0, MAX_PRODUCTS);
1467
+ if (cards.length === 0) {
1468
+ log("product_recommendation (inline) rendered with zero products — skipping", "warn");
1469
+ return false;
1470
+ }
1471
+ const layout = ic.rec_layout || "row";
1472
+ const template = resolveGlobalTemplate(ic);
1473
+ const fg = "#0f172a";
1474
+ const accent = sanitizeColor(campaign.background_color || "#4169e1");
1475
+ addAnimationStyles();
1476
+ const section = document.createElement("section");
1477
+ section.className = "aegis-in-app-product-rec-inline";
1478
+ section.setAttribute("data-campaign-id", campaign.id);
1479
+ section.style.cssText = `
1480
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1481
+ color: ${fg};
1482
+ `;
1483
+ if (campaign.title) {
1484
+ const title = document.createElement("h2");
1485
+ title.textContent = campaign.title;
1486
+ title.style.cssText = "font-weight: 700; font-size: 16px; margin: 0 0 2px;";
1487
+ section.appendChild(title);
1488
+ }
1489
+ if (campaign.body) {
1490
+ const body = document.createElement("div");
1491
+ body.textContent = campaign.body;
1492
+ body.style.cssText = "font-size: 13px; opacity: 0.65; line-height: 1.4; margin-bottom: 10px;";
1493
+ section.appendChild(body);
1494
+ }
1495
+ const grid = document.createElement("div");
1496
+ const isRow = layout !== "grid";
1497
+ if (layout === "grid") {
1498
+ grid.style.cssText = "display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;";
1499
+ } else {
1500
+ grid.style.cssText = `
1501
+ display: flex; gap: 10px; overflow-x: auto;
1502
+ scroll-snap-type: x proximity; padding: 0 0 6px;
1503
+ -webkit-overflow-scrolling: touch; scrollbar-width: none;
1504
+ `;
1505
+ grid.style.msOverflowStyle = "none";
1506
+ }
1507
+ cards.forEach((c) => {
1508
+ grid.appendChild(
1509
+ renderCardFromTemplate(c, { template, ctx, action: "cart", layout, layoutAccent: accent })
1510
+ );
1511
+ });
1512
+ if (isRow && cards.length > 2) {
1513
+ const wrap = document.createElement("div");
1514
+ wrap.style.cssText = "position: relative;";
1515
+ wrap.appendChild(grid);
1516
+ const arrow = document.createElement("button");
1517
+ arrow.setAttribute("aria-label", "Scroll for more");
1518
+ arrow.innerHTML = "›";
1519
+ arrow.style.cssText = `
1520
+ position: absolute; top: 50%; right: 0; transform: translateY(-50%);
1521
+ width: 30px; height: 30px; border-radius: 999px; border: 1px solid ${fg}1a;
1522
+ background: #fff; color: ${fg}; font-size: 18px; line-height: 1; cursor: pointer;
1523
+ box-shadow: 0 2px 8px rgba(0,0,0,0.12); display: flex; align-items: center;
1524
+ justify-content: center; z-index: 2;
1525
+ `;
1526
+ arrow.addEventListener("click", (e) => {
1527
+ e.stopPropagation();
1528
+ grid.scrollBy({ left: Math.round(grid.clientWidth * 0.8), behavior: "smooth" });
1529
+ });
1530
+ const syncArrow = () => {
1531
+ const atEnd = grid.scrollLeft + grid.clientWidth >= grid.scrollWidth - 8;
1532
+ arrow.style.display = atEnd ? "none" : "flex";
1533
+ };
1534
+ grid.addEventListener("scroll", syncArrow, { passive: true });
1535
+ setTimeout(syncArrow, 0);
1536
+ wrap.appendChild(arrow);
1537
+ section.appendChild(wrap);
1538
+ } else {
1539
+ section.appendChild(grid);
1540
+ }
1541
+ target.appendChild(section);
1542
+ return true;
1543
+ }
897
1544
  const MAX_GROUPS = 12;
898
1545
  const MAX_FRAMES = 20;
899
1546
  const DEFAULT_DURATION_MS = 5e3;
@@ -997,7 +1644,7 @@ function renderLauncherBar(ctx, cfg, target) {
997
1644
  if (floating) {
998
1645
  const dismiss = document.createElement("button");
999
1646
  dismiss.type = "button";
1000
- dismiss.textContent = "";
1647
+ dismiss.innerHTML = lucideSvg("x", { size: 14 });
1001
1648
  dismiss.setAttribute("aria-label", "Dismiss stories");
1002
1649
  dismiss.style.cssText = `
1003
1650
  flex: 0 0 auto; align-self: center; margin-left: auto;
@@ -1141,7 +1788,7 @@ function openViewer(ctx, cfg, startGroupIndex, startFrameIndex = 0) {
1141
1788
  };
1142
1789
  const close = document.createElement("button");
1143
1790
  close.type = "button";
1144
- close.textContent = "";
1791
+ close.innerHTML = lucideSvg("x", { size: 18 });
1145
1792
  close.setAttribute("aria-label", "Close");
1146
1793
  close.style.cssText = "position: absolute; top: 12px; right: 10px; z-index: 5; width: 34px; height: 34px; border: none; border-radius: 999px; background: rgba(0,0,0,0.32); color: #fff; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; line-height: 1;";
1147
1794
  stage.appendChild(close);
@@ -1418,7 +2065,7 @@ function openGameModal(ctx, className, maxWidth = 360) {
1418
2065
  };
1419
2066
  const close = document.createElement("button");
1420
2067
  close.type = "button";
1421
- close.textContent = "";
2068
+ close.innerHTML = lucideSvg("x", { size: 15 });
1422
2069
  close.setAttribute("aria-label", "Close");
1423
2070
  close.style.cssText = `position:absolute;top:10px;right:10px;width:30px;height:30px;border:none;border-radius:999px;background:${branded ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.05)"};color:${branded ? "#fff" : "#475569"};font-size:15px;cursor:pointer;z-index:1;`;
1424
2071
  close.addEventListener("click", () => dismiss(true));
@@ -2095,7 +2742,7 @@ function renderGame(ctx, subType) {
2095
2742
  fn(ctx);
2096
2743
  return true;
2097
2744
  }
2098
- const ASPECT = { "16:9": "16 / 9", "9:16": "9 / 16", "1:1": "1 / 1", "4:5": "4 / 5" };
2745
+ const ASPECT$1 = { "16:9": "16 / 9", "9:16": "9 / 16", "1:1": "1 / 1", "4:5": "4 / 5" };
2099
2746
  function buildVideo(ctx, aspect, autoplay, loop) {
2100
2747
  var _a;
2101
2748
  const { campaign, sanitizeUrl } = ctx;
@@ -2103,7 +2750,7 @@ function buildVideo(ctx, aspect, autoplay, loop) {
2103
2750
  const videoUrl = sanitizeUrl(ic.video_url || campaign.video_url || "");
2104
2751
  const poster = campaign.image_url ? sanitizeUrl(campaign.image_url) : null;
2105
2752
  const wrap = document.createElement("div");
2106
- wrap.style.cssText = `position:relative;width:100%;aspect-ratio:${ASPECT[aspect] || ASPECT["16:9"]};background:#0b1220;border-radius:14px;overflow:hidden;`;
2753
+ wrap.style.cssText = `position:relative;width:100%;aspect-ratio:${ASPECT$1[aspect] || ASPECT$1["16:9"]};background:#0b1220;border-radius:14px;overflow:hidden;`;
2107
2754
  if (videoUrl) {
2108
2755
  const vid = document.createElement("video");
2109
2756
  vid.src = videoUrl;
@@ -2136,14 +2783,15 @@ function buildVideo(ctx, aspect, autoplay, loop) {
2136
2783
  });
2137
2784
  const pill = document.createElement("button");
2138
2785
  pill.type = "button";
2139
- pill.textContent = "🔇";
2786
+ pill.innerHTML = lucideSvg("volume-x", { size: 14 });
2140
2787
  pill.setAttribute("aria-label", "Unmute");
2141
2788
  pill.style.cssText = "position:absolute;bottom:10px;right:10px;z-index:2;width:34px;height:34px;border:none;border-radius:999px;background:rgba(0,0,0,0.45);color:#fff;font-size:14px;cursor:pointer;";
2142
2789
  pill.addEventListener("click", (e) => {
2143
2790
  var _a2;
2144
2791
  e.stopPropagation();
2145
2792
  vid.muted = !vid.muted;
2146
- pill.textContent = vid.muted ? "🔇" : "🔊";
2793
+ pill.innerHTML = lucideSvg(vid.muted ? "volume-x" : "volume-2", { size: 14 });
2794
+ pill.setAttribute("aria-label", vid.muted ? "Unmute" : "Mute");
2147
2795
  if (!vid.muted) void ((_a2 = vid.play) == null ? void 0 : _a2.call(vid).catch(() => {
2148
2796
  }));
2149
2797
  });
@@ -2231,7 +2879,7 @@ function renderVideo(ctx) {
2231
2879
  }
2232
2880
  const close = document.createElement("button");
2233
2881
  close.type = "button";
2234
- close.textContent = "";
2882
+ close.innerHTML = lucideSvg("x", { size: 15 });
2235
2883
  close.setAttribute("aria-label", "Close");
2236
2884
  close.style.cssText = "position:absolute;top:10px;left:10px;z-index:3;width:32px;height:32px;border:none;border-radius:999px;background:rgba(0,0,0,0.4);color:#fff;font-size:15px;cursor:pointer;";
2237
2885
  close.addEventListener("click", () => {
@@ -2272,11 +2920,187 @@ function renderVideoInline(ctx, target) {
2272
2920
  target.appendChild(banner);
2273
2921
  return true;
2274
2922
  }
2923
+ const HEX = /^#[0-9a-fA-F]{3,8}$/;
2924
+ function safeColor(c, fallback) {
2925
+ return c && HEX.test(c) ? c : fallback;
2926
+ }
2927
+ const ASPECT = { "16:9": "16 / 9", "4:3": "4 / 3", "21:9": "21 / 9" };
2928
+ function _injectHeroResponsiveCss() {
2929
+ if (typeof document === "undefined") return;
2930
+ if (document.getElementById("aegis-hero-style")) return;
2931
+ const style = document.createElement("style");
2932
+ style.id = "aegis-hero-style";
2933
+ style.textContent = ".aegis-hero-root{border-radius:16px;}@media (min-width:768px){.aegis-hero-root{border-radius:0;min-height:45vh;max-height:70vh;}}";
2934
+ document.head.appendChild(style);
2935
+ }
2936
+ function cardHeadline(c) {
2937
+ return c.headline ?? c.title ?? "";
2938
+ }
2939
+ function cardSub(c) {
2940
+ return c.subhead ?? c.body ?? "";
2941
+ }
2942
+ function cardCta(c) {
2943
+ return c.cta_label ?? c.cta_text ?? "";
2944
+ }
2945
+ function cardUrl(c) {
2946
+ if (c.cta_url) return c.cta_url;
2947
+ const t = c.cta_target;
2948
+ if (t && t.value) return t.type === "category" ? `?category=${encodeURIComponent(t.value)}` : t.value;
2949
+ return "";
2950
+ }
2951
+ function renderHeroInline(ctx, target) {
2952
+ const { campaign, sanitizeUrl, trackEvent } = ctx;
2953
+ const ic = campaign.interactive_config || {};
2954
+ const cards = Array.isArray(ic.cards) ? ic.cards : [];
2955
+ if (cards.length === 0) return false;
2956
+ const chrome = ic.chrome || {};
2957
+ const accent = safeColor(
2958
+ chrome.accent && chrome.accent !== "brand" ? chrome.accent : campaign.background_color,
2959
+ "#4169e1"
2960
+ );
2961
+ const variant = chrome.variant ?? "hero_fullbleed";
2962
+ if (variant === "announcement_bar") {
2963
+ const c = cards[0];
2964
+ const bar = document.createElement("div");
2965
+ bar.style.cssText = `display:flex;align-items:center;justify-content:center;gap:10px;background:${accent};color:#fff;padding:8px 14px;font:600 13px/1.3 Inter,system-ui,sans-serif;text-align:center;`;
2966
+ bar.textContent = [cardHeadline(c), cardSub(c)].filter(Boolean).join(" · ");
2967
+ const url = sanitizeUrl(cardUrl(c));
2968
+ if (url && cardCta(c)) {
2969
+ const a = document.createElement("span");
2970
+ a.style.cssText = "text-decoration:underline;cursor:pointer;white-space:nowrap;";
2971
+ a.textContent = cardCta(c);
2972
+ a.addEventListener("click", () => {
2973
+ var _a;
2974
+ trackEvent(campaign.id, "clicked");
2975
+ (_a = ctx.navigate) == null ? void 0 : _a.call(ctx, url);
2976
+ });
2977
+ bar.appendChild(a);
2978
+ }
2979
+ target.appendChild(bar);
2980
+ trackEvent(campaign.id, "impression");
2981
+ return true;
2982
+ }
2983
+ _injectHeroResponsiveCss();
2984
+ const root = document.createElement("div");
2985
+ root.className = "aegis-hero-root";
2986
+ root.style.cssText = `position:relative;width:100%;overflow:hidden;box-shadow:0 1px 2px rgba(16,24,40,0.06);${chrome.aspect_ratio && ASPECT[chrome.aspect_ratio] ? `aspect-ratio:${ASPECT[chrome.aspect_ratio]};` : "min-height:300px;"}`;
2987
+ root.setAttribute("data-campaign-id", campaign.id);
2988
+ const track = document.createElement("div");
2989
+ track.style.cssText = "display:flex;height:100%;width:100%;transition:transform 0.5s ease;";
2990
+ root.appendChild(track);
2991
+ const overlayCss = chrome.overlay === "none" ? "" : chrome.overlay === "full-scrim" ? "background:linear-gradient(0deg,rgba(0,0,0,0.55),rgba(0,0,0,0.25));" : "background:linear-gradient(to top right,rgba(0,0,0,0.7),rgba(0,0,0,0.25) 45%,transparent);";
2992
+ const justify = chrome.text_position === "center" ? "center" : "flex-end";
2993
+ const align = chrome.text_position === "bottom-left" || !chrome.text_position ? "flex-start" : "center";
2994
+ cards.forEach((c, i) => {
2995
+ const slide = document.createElement("div");
2996
+ slide.style.cssText = "position:relative;flex:0 0 100%;width:100%;height:100%;min-height:inherit;background:#f1f5f9;";
2997
+ const videoUrl = sanitizeUrl(c.video_url ?? "");
2998
+ const isVideo = !!videoUrl || c.media_type === "video";
2999
+ const mediaUrl = isVideo ? videoUrl || sanitizeUrl(c.media_url ?? "") : sanitizeUrl(c.image_url ?? c.media_url ?? "");
3000
+ if (mediaUrl) {
3001
+ if (isVideo) {
3002
+ const v = document.createElement("video");
3003
+ v.src = mediaUrl;
3004
+ v.autoplay = true;
3005
+ v.muted = true;
3006
+ v.loop = true;
3007
+ v.playsInline = true;
3008
+ v.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-fit:cover;";
3009
+ slide.appendChild(v);
3010
+ } else {
3011
+ const img = document.createElement("div");
3012
+ img.style.cssText = `position:absolute;inset:0;background:url("${mediaUrl}") center/cover no-repeat;`;
3013
+ slide.appendChild(img);
3014
+ }
3015
+ }
3016
+ if (overlayCss) {
3017
+ const scrim = document.createElement("div");
3018
+ scrim.style.cssText = `position:absolute;inset:0;${overlayCss}`;
3019
+ slide.appendChild(scrim);
3020
+ }
3021
+ const content = document.createElement("div");
3022
+ content.style.cssText = `position:absolute;inset:0;display:flex;flex-direction:column;justify-content:${justify};align-items:${align};gap:6px;padding:24px;text-align:${align === "center" ? "center" : "left"};`;
3023
+ const h = cardHeadline(c);
3024
+ if (h) {
3025
+ const head = document.createElement("h2");
3026
+ head.textContent = h;
3027
+ head.style.cssText = "margin:0;color:#fff;font:800 24px/1.2 Inter Tight,Inter,system-ui,sans-serif;letter-spacing:-0.02em;text-shadow:0 1px 8px rgba(0,0,0,0.35);max-width:90%;";
3028
+ content.appendChild(head);
3029
+ }
3030
+ const s = cardSub(c);
3031
+ if (s) {
3032
+ const sub = document.createElement("p");
3033
+ sub.textContent = s;
3034
+ sub.style.cssText = "margin:0;color:rgba(255,255,255,0.92);font:500 14px/1.4 Inter,system-ui,sans-serif;text-shadow:0 1px 6px rgba(0,0,0,0.3);max-width:90%;";
3035
+ content.appendChild(sub);
3036
+ }
3037
+ const cta = cardCta(c);
3038
+ const url = sanitizeUrl(cardUrl(c));
3039
+ if (cta && chrome.cta_style !== "none") {
3040
+ const btn = document.createElement("button");
3041
+ btn.textContent = cta;
3042
+ btn.style.cssText = chrome.cta_style === "underline" ? `margin-top:6px;background:none;border:none;color:#fff;font:700 14px Inter,system-ui,sans-serif;text-decoration:underline;cursor:pointer;padding:0;` : `margin-top:8px;background:${accent};color:#fff;border:none;border-radius:999px;padding:9px 18px;font:700 13px Inter,system-ui,sans-serif;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,0.2);`;
3043
+ btn.addEventListener("click", () => {
3044
+ var _a;
3045
+ trackEvent(campaign.id, "clicked", { stepId: `card_${i}` });
3046
+ if (url) (_a = ctx.navigate) == null ? void 0 : _a.call(ctx, url);
3047
+ });
3048
+ content.appendChild(btn);
3049
+ }
3050
+ slide.appendChild(content);
3051
+ track.appendChild(slide);
3052
+ });
3053
+ const dotEls = [];
3054
+ let activeIdx = 0;
3055
+ const goto = (idx) => {
3056
+ activeIdx = (idx % cards.length + cards.length) % cards.length;
3057
+ track.style.transform = `translateX(-${activeIdx * 100}%)`;
3058
+ dotEls.forEach((d, i) => {
3059
+ d.style.width = i === activeIdx ? "18px" : "6px";
3060
+ d.style.background = i === activeIdx ? accent : "rgba(255,255,255,0.7)";
3061
+ });
3062
+ };
3063
+ const dots = document.createElement("div");
3064
+ dots.style.cssText = "position:absolute;bottom:8px;left:50%;transform:translateX(-50%);display:flex;gap:6px;z-index:2;";
3065
+ if (cards.length > 1) {
3066
+ cards.forEach((_, i) => {
3067
+ const dot = document.createElement("span");
3068
+ dot.style.cssText = "width:6px;height:6px;border-radius:999px;background:rgba(255,255,255,0.7);transition:all 0.25s;cursor:pointer;";
3069
+ dot.addEventListener("click", () => goto(i));
3070
+ dotEls.push(dot);
3071
+ dots.appendChild(dot);
3072
+ });
3073
+ root.appendChild(dots);
3074
+ }
3075
+ target.appendChild(root);
3076
+ goto(0);
3077
+ if (cards.length > 1 && chrome.loop !== false) {
3078
+ const ms = typeof chrome.autoplay_ms === "number" && chrome.autoplay_ms >= 1500 ? chrome.autoplay_ms : 4e3;
3079
+ let timer = window.setInterval(() => goto(activeIdx + 1), ms);
3080
+ root.addEventListener("mouseenter", () => window.clearInterval(timer));
3081
+ root.addEventListener("mouseleave", () => {
3082
+ timer = window.setInterval(() => goto(activeIdx + 1), ms);
3083
+ });
3084
+ }
3085
+ trackEvent(campaign.id, "impression");
3086
+ return true;
3087
+ }
2275
3088
  const STYLE_ID = "aegis-chat-styles";
2276
3089
  const POLL_INTERVAL_MS = 5e3;
3090
+ let currentLauncher = null;
3091
+ function getCurrentLauncher() {
3092
+ return currentLauncher;
3093
+ }
3094
+ function openChat(prefill) {
3095
+ currentLauncher == null ? void 0 : currentLauncher.openPanel(prefill);
3096
+ }
3097
+ function closeChat() {
3098
+ currentLauncher == null ? void 0 : currentLauncher.closePanel();
3099
+ }
2277
3100
  const ATTENTION_DELAY_MS = 4500;
2278
3101
  class AegisChat {
2279
3102
  constructor(config) {
3103
+ this.sessionResumed = false;
2280
3104
  this.open = false;
2281
3105
  this.initialized = false;
2282
3106
  this.unread = 0;
@@ -2294,12 +3118,14 @@ class AegisChat {
2294
3118
  this.icon = config.icon ?? "sparkle";
2295
3119
  this.logoUrl = config.logoUrl;
2296
3120
  this.position = config.position ?? "bottom-right";
3121
+ this.displayMode = config.displayMode ?? "bubble";
2297
3122
  this.agentPersona = config.agentPersona;
2298
3123
  this.quickReplies = config.quickReplies;
2299
3124
  this.onSessionStart = config.onSessionStart;
2300
3125
  this.onSessionEnd = config.onSessionEnd;
2301
3126
  this.onMessageSent = config.onMessageSent;
2302
3127
  this.anonymousId = this.readAnonId();
3128
+ this.contactId = this.contactId ?? this.readStoredContactId();
2303
3129
  }
2304
3130
  // ── Lifecycle ────────────────────────────────────────────────────────────
2305
3131
  initialize() {
@@ -2307,7 +3133,8 @@ class AegisChat {
2307
3133
  this.initialized = true;
2308
3134
  this.injectStyles();
2309
3135
  this.mount();
2310
- this.scheduleAttention();
3136
+ currentLauncher = this;
3137
+ if (this.displayMode !== "nav") this.scheduleAttention();
2311
3138
  }
2312
3139
  /** Called by the runtime when the visitor identifies. */
2313
3140
  updateContactId(contactId) {
@@ -2330,7 +3157,7 @@ class AegisChat {
2330
3157
  (_c = this.bubble) == null ? void 0 : _c.classList.add("aegis-chat-bubble--hidden");
2331
3158
  this.clearUnread();
2332
3159
  if (prefill && this.input) this.input.value = prefill;
2333
- void this.refreshHistory();
3160
+ void this.resumeSession();
2334
3161
  this.startPolling();
2335
3162
  (_d = this.input) == null ? void 0 : _d.focus();
2336
3163
  }
@@ -2355,13 +3182,22 @@ class AegisChat {
2355
3182
  this.stopPolling();
2356
3183
  (_a = this.root) == null ? void 0 : _a.remove();
2357
3184
  this.initialized = false;
3185
+ if (currentLauncher === this) currentLauncher = null;
2358
3186
  }
2359
3187
  // ── Send ─────────────────────────────────────────────────────────────────
2360
- async send(text) {
3188
+ async send(text, media) {
2361
3189
  var _a;
2362
- const message = text.trim();
2363
- if (!message) return;
2364
- this.appendBubble({ id: `local_${Date.now()}`, sender: "customer", role: "user", content: message });
3190
+ const caption2 = text.trim();
3191
+ if (!caption2 && !media) return;
3192
+ const message = caption2 || (media ? media.type === "image" ? "Sent a photo 📷" : "Sent a file 📎" : "");
3193
+ this.appendBubble({
3194
+ id: `local_${Date.now()}`,
3195
+ sender: "customer",
3196
+ role: "user",
3197
+ content: message,
3198
+ media_url: (media == null ? void 0 : media.url) ?? null,
3199
+ media_type: (media == null ? void 0 : media.type) ?? null
3200
+ });
2365
3201
  this.pendingEcho.push(message);
2366
3202
  if (this.input) {
2367
3203
  this.input.value = "";
@@ -2377,6 +3213,8 @@ class AegisChat {
2377
3213
  channel: this.channel,
2378
3214
  anonymous_id: this.anonymousId,
2379
3215
  contact_id: this.contactId,
3216
+ media_url: media == null ? void 0 : media.url,
3217
+ media_type: media == null ? void 0 : media.type,
2380
3218
  // Active Web Chat — binds the campaign-configured persona. Optional;
2381
3219
  // the backend falls back to the tenant default if unknown/absent
2382
3220
  // (honored server-side in Phase E). JSON.stringify drops it if absent.
@@ -2395,6 +3233,7 @@ class AegisChat {
2395
3233
  } catch {
2396
3234
  }
2397
3235
  this.contactId = data.contact_id;
3236
+ this.persistContactId(data.contact_id);
2398
3237
  this.sseTicket = data.sse_ticket;
2399
3238
  this.connectSSE(data.sse_ticket);
2400
3239
  void this.refreshHistory();
@@ -2403,6 +3242,51 @@ class AegisChat {
2403
3242
  this.appendSystem("Sorry — your message could not be delivered. Please try again.");
2404
3243
  }
2405
3244
  }
3245
+ // ── Media attachments ─────────────────────────────────────────────────────
3246
+ /** Validate + upload a picked file, then send it as a media message. */
3247
+ async handleFile(file) {
3248
+ var _a;
3249
+ const allowed = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp", "application/pdf"]);
3250
+ if (!allowed.has(file.type)) {
3251
+ this.appendSystem("That file type isn’t supported (images or PDF only).");
3252
+ return;
3253
+ }
3254
+ if (file.size > 8 * 1024 * 1024) {
3255
+ this.appendSystem("That file is too large (max 8 MB).");
3256
+ return;
3257
+ }
3258
+ try {
3259
+ const data = await this.fileToBase64(file);
3260
+ const res = await fetch(`${this.apiHost}/v1/chat/upload`, {
3261
+ method: "POST",
3262
+ headers: { "Content-Type": "application/json", "X-Aegis-Write-Key": this.writeKey },
3263
+ body: JSON.stringify({ data, content_type: file.type, filename: file.name })
3264
+ });
3265
+ if (!res.ok) {
3266
+ this.log("upload failed", res.status);
3267
+ this.appendSystem("Sorry — that attachment couldn’t be uploaded.");
3268
+ return;
3269
+ }
3270
+ const out = await res.json();
3271
+ await this.send(((_a = this.input) == null ? void 0 : _a.value) ?? "", { url: out.media_url, type: out.media_type });
3272
+ } catch (err) {
3273
+ this.log("upload error", err);
3274
+ this.appendSystem("Sorry — that attachment couldn’t be uploaded.");
3275
+ }
3276
+ }
3277
+ /** Read a File as bare base64 (strip the `data:…;base64,` prefix). */
3278
+ fileToBase64(file) {
3279
+ return new Promise((resolve, reject) => {
3280
+ const reader = new FileReader();
3281
+ reader.onload = () => {
3282
+ const result = String(reader.result || "");
3283
+ const comma = result.indexOf(",");
3284
+ resolve(comma >= 0 ? result.slice(comma + 1) : result);
3285
+ };
3286
+ reader.onerror = () => reject(reader.error);
3287
+ reader.readAsDataURL(file);
3288
+ });
3289
+ }
2406
3290
  // ── Reply transport: SSE nudge + history refetch (poll fallback) ──────────
2407
3291
  connectSSE(ticket) {
2408
3292
  if (!this.open) return;
@@ -2522,7 +3406,7 @@ class AegisChat {
2522
3406
  const closeBtn = document.createElement("button");
2523
3407
  closeBtn.className = "aegis-chat-close";
2524
3408
  closeBtn.setAttribute("aria-label", "Close chat");
2525
- closeBtn.textContent = "";
3409
+ closeBtn.innerHTML = lucideSvg("x", { size: 16 });
2526
3410
  closeBtn.addEventListener("click", () => this.closePanel());
2527
3411
  header.appendChild(titleEl);
2528
3412
  header.appendChild(closeBtn);
@@ -2540,6 +3424,21 @@ class AegisChat {
2540
3424
  void this.send(input.value);
2541
3425
  }
2542
3426
  });
3427
+ const fileInput = document.createElement("input");
3428
+ fileInput.type = "file";
3429
+ fileInput.accept = "image/*,application/pdf";
3430
+ fileInput.className = "aegis-chat-fileinput";
3431
+ fileInput.addEventListener("change", () => {
3432
+ const f = fileInput.files && fileInput.files[0];
3433
+ if (f) void this.handleFile(f);
3434
+ fileInput.value = "";
3435
+ });
3436
+ const attachBtn = document.createElement("button");
3437
+ attachBtn.type = "button";
3438
+ attachBtn.className = "aegis-chat-attach";
3439
+ attachBtn.setAttribute("aria-label", "Attach a file");
3440
+ attachBtn.innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>';
3441
+ attachBtn.addEventListener("click", () => fileInput.click());
2543
3442
  const sendBtn = document.createElement("button");
2544
3443
  sendBtn.type = "submit";
2545
3444
  sendBtn.className = "aegis-chat-send";
@@ -2549,8 +3448,10 @@ class AegisChat {
2549
3448
  e.preventDefault();
2550
3449
  void this.send(input.value);
2551
3450
  });
3451
+ composer.appendChild(attachBtn);
2552
3452
  composer.appendChild(input);
2553
3453
  composer.appendChild(sendBtn);
3454
+ composer.appendChild(fileInput);
2554
3455
  panel.appendChild(header);
2555
3456
  panel.appendChild(thread);
2556
3457
  panel.appendChild(composer);
@@ -2561,7 +3462,7 @@ class AegisChat {
2561
3462
  const tipClose = document.createElement("button");
2562
3463
  tipClose.className = "aegis-chat-tip-close";
2563
3464
  tipClose.setAttribute("aria-label", "Dismiss");
2564
- tipClose.textContent = "";
3465
+ tipClose.innerHTML = lucideSvg("x", { size: 14 });
2565
3466
  tipClose.addEventListener("click", (e) => {
2566
3467
  e.stopPropagation();
2567
3468
  this.dismissAttention();
@@ -2570,8 +3471,10 @@ class AegisChat {
2570
3471
  tip.appendChild(tipClose);
2571
3472
  tip.addEventListener("click", () => this.openPanel());
2572
3473
  root.appendChild(panel);
2573
- root.appendChild(tip);
2574
- root.appendChild(bubble);
3474
+ if (this.displayMode !== "nav") {
3475
+ root.appendChild(tip);
3476
+ root.appendChild(bubble);
3477
+ }
2575
3478
  document.body.appendChild(root);
2576
3479
  this.greetingTip = tip;
2577
3480
  this.root = root;
@@ -2609,6 +3512,26 @@ class AegisChat {
2609
3512
  if (msg.id) this.renderedIds.add(msg.id);
2610
3513
  const row = document.createElement("div");
2611
3514
  row.className = `aegis-chat-msg aegis-chat-msg--${msg.sender}`;
3515
+ if (msg.media_url) {
3516
+ if (msg.media_type === "image") {
3517
+ const img = document.createElement("img");
3518
+ img.className = "aegis-chat-media-img";
3519
+ img.src = msg.media_url;
3520
+ img.alt = "attachment";
3521
+ img.loading = "lazy";
3522
+ const url = msg.media_url;
3523
+ img.addEventListener("click", () => window.open(url, "_blank", "noopener"));
3524
+ row.appendChild(img);
3525
+ } else {
3526
+ const a = document.createElement("a");
3527
+ a.className = "aegis-chat-media-file";
3528
+ a.href = msg.media_url;
3529
+ a.target = "_blank";
3530
+ a.rel = "noopener noreferrer";
3531
+ a.textContent = "📎 Attachment";
3532
+ row.appendChild(a);
3533
+ }
3534
+ }
2612
3535
  if (msg.content) {
2613
3536
  const bubble = document.createElement("div");
2614
3537
  bubble.className = "aegis-chat-bubbletext";
@@ -2700,6 +3623,54 @@ class AegisChat {
2700
3623
  return void 0;
2701
3624
  }
2702
3625
  }
3626
+ readStoredContactId() {
3627
+ if (typeof document === "undefined") return void 0;
3628
+ try {
3629
+ return new Storage().get("aegis_chat_cid") ?? void 0;
3630
+ } catch {
3631
+ return void 0;
3632
+ }
3633
+ }
3634
+ persistContactId(id) {
3635
+ if (!id || typeof document === "undefined") return;
3636
+ try {
3637
+ new Storage().set("aegis_chat_cid", id, 365);
3638
+ } catch {
3639
+ }
3640
+ }
3641
+ /** Restore a returning visitor's thread on open. Resolves an existing contact
3642
+ * + mints a per-session ticket WITHOUT sending a message (history is ticket-
3643
+ * gated). No-ops for brand-new visitors and once a ticket is already held. */
3644
+ async resumeSession() {
3645
+ if (this.sseTicket) {
3646
+ void this.refreshHistory();
3647
+ return;
3648
+ }
3649
+ if (this.sessionResumed) return;
3650
+ this.sessionResumed = true;
3651
+ if (!this.contactId && !this.anonymousId) return;
3652
+ try {
3653
+ const res = await fetch(`${this.apiHost}/v1/chat/session`, {
3654
+ method: "POST",
3655
+ headers: { "Content-Type": "application/json", "X-Aegis-Write-Key": this.writeKey },
3656
+ body: JSON.stringify({
3657
+ anonymous_id: this.anonymousId,
3658
+ contact_id: this.contactId,
3659
+ channel: this.channel
3660
+ })
3661
+ });
3662
+ if (!res.ok) return;
3663
+ const data = await res.json();
3664
+ if (!data.contact_id || !data.sse_ticket) return;
3665
+ this.contactId = data.contact_id;
3666
+ this.sseTicket = data.sse_ticket;
3667
+ this.persistContactId(data.contact_id);
3668
+ await this.refreshHistory();
3669
+ if (this.open) this.connectSSE(data.sse_ticket);
3670
+ } catch (err) {
3671
+ this.log("resume session failed", err);
3672
+ }
3673
+ }
2703
3674
  // ── Typing indicator ───────────────────────────────────────────────────────
2704
3675
  showTyping() {
2705
3676
  if (!this.thread || this.typingEl) return;
@@ -2800,11 +3771,19 @@ const CHAT_CSS = `
2800
3771
  .aegis-chat-input{flex:1;resize:none;border:1px solid #e2e8f0;border-radius:12px;padding:9px 12px;font-size:14px;font-family:inherit;max-height:96px;outline:none}
2801
3772
  .aegis-chat-input:focus{border-color:var(--aegis-chat-accent,#4169e1)}
2802
3773
  .aegis-chat-send{width:40px;height:40px;border-radius:9999px;border:none;background:var(--aegis-chat-accent,#4169e1);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}
3774
+ .aegis-chat-attach{width:38px;height:38px;border-radius:9999px;border:none;background:transparent;color:#64748b;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto;transition:background .15s ease,color .15s ease}
3775
+ .aegis-chat-attach:hover{background:#f1f5f9;color:var(--aegis-chat-accent,#4169e1)}
3776
+ .aegis-chat-fileinput{display:none}
3777
+ .aegis-chat-media-img{max-width:200px;max-height:200px;border-radius:12px;object-fit:cover;cursor:pointer;display:block}
3778
+ .aegis-chat-media-file{display:inline-flex;align-items:center;gap:6px;padding:8px 12px;border-radius:12px;background:#fff;border:1px solid #e2e8f0;font-size:13px;color:#0f172a;text-decoration:none;max-width:220px}
3779
+ .aegis-chat-msg--customer .aegis-chat-media-file{background:rgba(255,255,255,.18);border-color:transparent;color:#fff}
2803
3780
  /* Mobile: raise the bubble above the storefront's full-width cart bar so they
2804
3781
  never overlap; widen the panel and reduce the tooltip width. */
2805
- @media (max-width:480px){
3782
+ @media (max-width:767px){
2806
3783
  .aegis-chat-root{bottom:84px;right:16px}
2807
- .aegis-chat-panel{width:calc(100vw - 24px);height:calc(100vh - 140px)}
3784
+ /* Full-page chat on mobile — the open panel covers the whole viewport (incl.
3785
+ the bottom nav) like a native chat screen, instead of a floating card. */
3786
+ .aegis-chat-panel{position:fixed;inset:0;left:0;right:0;width:100vw;height:100vh;height:100dvh;max-width:none;max-height:none;border-radius:0}
2808
3787
  .aegis-chat-tip{max-width:200px}
2809
3788
  }
2810
3789
  `;
@@ -3376,6 +4355,10 @@ function renderActiveWebChat(ctx, creds) {
3376
4355
  icon: icon === "sparkle" || icon === "chat" || icon === "logo" ? icon : void 0,
3377
4356
  logoUrl: logo ? ctx.sanitizeUrl(logo) ?? void 0 : void 0,
3378
4357
  position: ic.chat_position === "bottom-left" ? "bottom-left" : void 0,
4358
+ // 'nav' = headless (no bubble); the host opens it via aegis.chat.open() or a
4359
+ // client_trigger. Defaults to 'bubble'. Per-device override via device_type
4360
+ // targeting on the campaign (e.g. a mobile campaign with chat_display_mode='nav').
4361
+ displayMode: ic.chat_display_mode === "nav" ? "nav" : void 0,
3379
4362
  agentPersona: str(ic.chat_agent_persona),
3380
4363
  quickReplies: Array.isArray(ic.chat_quick_replies) ? ic.chat_quick_replies.filter((q) => typeof q === "string") : void 0,
3381
4364
  // Bridge the engine's session lifecycle to campaign analytics. Per the
@@ -3436,6 +4419,7 @@ const _AegisInAppManager = class _AegisInAppManager {
3436
4419
  constructor(config) {
3437
4420
  this.campaigns = [];
3438
4421
  this.displayedCampaigns = /* @__PURE__ */ new Set();
4422
+ this._displaySizeMul = 1;
3439
4423
  this.suppressedUntil = /* @__PURE__ */ new Map();
3440
4424
  this.isInitialized = false;
3441
4425
  this.reconnectAttempts = 0;
@@ -3444,10 +4428,15 @@ const _AegisInAppManager = class _AegisInAppManager {
3444
4428
  this.ready = new Promise((resolve) => {
3445
4429
  this.readyResolve = resolve;
3446
4430
  });
4431
+ this.pendingPreInitRefresh = false;
3447
4432
  this.currentSurface = null;
3448
4433
  this.currentScreen = null;
3449
4434
  this.currentLocationId = null;
3450
4435
  this.filledSlots = /* @__PURE__ */ new WeakSet();
4436
+ this.slotSignatures = /* @__PURE__ */ new WeakMap();
4437
+ this.slotTimers = /* @__PURE__ */ new WeakMap();
4438
+ this.slotObserver = null;
4439
+ this.slotRenderScheduled = false;
3451
4440
  this.writeKey = config.writeKey;
3452
4441
  this.apiHost = config.apiHost || "https://api.aegis.ai";
3453
4442
  this.userId = config.userId ?? readAnonIdFromStorage$1();
@@ -3559,6 +4548,11 @@ const _AegisInAppManager = class _AegisInAppManager {
3559
4548
  this.connectSSE();
3560
4549
  }
3561
4550
  this.isInitialized = true;
4551
+ if (this.pendingPreInitRefresh) {
4552
+ this.pendingPreInitRefresh = false;
4553
+ this.log("Re-fetching campaigns for surface/screen declared during init");
4554
+ void this.refreshCampaigns();
4555
+ }
3562
4556
  this.log("AegisInApp initialized successfully");
3563
4557
  }
3564
4558
  updateUserId(userId) {
@@ -3607,16 +4601,17 @@ const _AegisInAppManager = class _AegisInAppManager {
3607
4601
  * change `currentSurface`, so arbitrary events never poison the surface gate.
3608
4602
  */
3609
4603
  refreshOnEvent(eventName, isScreenDeclaration = false) {
3610
- if (!this.isInitialized) {
3611
- this.log(`refreshOnEvent(${eventName}) before initialize — ignored`);
3612
- return;
3613
- }
3614
4604
  if (eventName && _AegisInAppManager.KNOWN_SURFACES.has(eventName)) {
3615
4605
  this.currentSurface = eventName;
3616
4606
  }
3617
4607
  if (isScreenDeclaration && eventName && eventName !== "page_view") {
3618
4608
  this.currentScreen = eventName;
3619
4609
  }
4610
+ if (!this.isInitialized) {
4611
+ this.pendingPreInitRefresh = true;
4612
+ this.log(`refreshOnEvent(${eventName}) before initialize — surface captured, refetch deferred to initialize()`);
4613
+ return;
4614
+ }
3620
4615
  if (this.refreshDebounceTimer) {
3621
4616
  clearTimeout(this.refreshDebounceTimer);
3622
4617
  }
@@ -3834,7 +4829,7 @@ const _AegisInAppManager = class _AegisInAppManager {
3834
4829
  try {
3835
4830
  const context = new URLSearchParams({
3836
4831
  device_type: this.detectDeviceType(),
3837
- page_url: typeof window !== "undefined" ? window.location.pathname : "/"
4832
+ page_url: typeof window !== "undefined" ? window.location.pathname + window.location.search : "/"
3838
4833
  });
3839
4834
  context.set("is_new_user", this.isNewUser() ? "true" : "false");
3840
4835
  if (this.currentSurface) {
@@ -3992,34 +4987,92 @@ const _AegisInAppManager = class _AegisInAppManager {
3992
4987
  if (!c.surface || c.surface.length === 0) return screenOk;
3993
4988
  return screenOk || this.matchesCurrentSurface(c);
3994
4989
  }
4990
+ // Re-fill slots that mount after a poll (SPA route changes). Observes the DOM
4991
+ // once; when a new [data-aegis-slot] appears it re-runs renderIntoSlots from
4992
+ // the cached campaigns (idempotent — already-filled slots are skipped). Does
4993
+ // NOT loop on the SDK's own injections (those add content INTO a slot, not a
4994
+ // new slot element).
4995
+ ensureSlotObserver() {
4996
+ if (this.slotObserver || typeof MutationObserver === "undefined" || typeof document === "undefined") {
4997
+ return;
4998
+ }
4999
+ this.slotObserver = new MutationObserver((mutations) => {
5000
+ let sawSlot = false;
5001
+ for (const m2 of mutations) {
5002
+ m2.addedNodes.forEach((n) => {
5003
+ var _a, _b;
5004
+ if (sawSlot || n.nodeType !== 1) return;
5005
+ const el = n;
5006
+ if (((_a = el.matches) == null ? void 0 : _a.call(el, "[data-aegis-slot]")) || ((_b = el.querySelector) == null ? void 0 : _b.call(el, "[data-aegis-slot]"))) {
5007
+ sawSlot = true;
5008
+ }
5009
+ });
5010
+ if (sawSlot) break;
5011
+ }
5012
+ if (!sawSlot || this.slotRenderScheduled) return;
5013
+ this.slotRenderScheduled = true;
5014
+ const run = () => {
5015
+ this.slotRenderScheduled = false;
5016
+ this.renderIntoSlots();
5017
+ };
5018
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
5019
+ else setTimeout(run, 0);
5020
+ });
5021
+ this.slotObserver.observe(document.body, { childList: true, subtree: true });
5022
+ }
3995
5023
  renderIntoSlots() {
3996
5024
  if (typeof document === "undefined") return;
5025
+ this.ensureSlotObserver();
3997
5026
  const slots = document.querySelectorAll("[data-aegis-slot]");
3998
5027
  if (slots.length === 0) return;
3999
5028
  const eligibleByCategory = /* @__PURE__ */ new Map();
5029
+ const seenIds = /* @__PURE__ */ new Set();
4000
5030
  for (const c of this.campaigns) {
4001
5031
  if (!this.matchesCurrentScreenOrSurface(c)) continue;
4002
5032
  const modes = c.delivery_modes;
4003
5033
  const category = c.widget_category;
4004
5034
  if (!modes || !modes.includes("embedded_card")) continue;
4005
5035
  if (!category) continue;
5036
+ if (seenIds.has(c.id)) continue;
5037
+ seenIds.add(c.id);
4006
5038
  const arr2 = eligibleByCategory.get(category);
4007
5039
  if (arr2) arr2.push(c);
4008
5040
  else eligibleByCategory.set(category, [c]);
4009
5041
  }
4010
5042
  if (eligibleByCategory.size === 0) return;
4011
5043
  slots.forEach((slot) => {
4012
- if (this.filledSlots.has(slot)) return;
4013
5044
  const key = slot.getAttribute("data-aegis-slot");
4014
5045
  if (!key) return;
4015
5046
  const list = eligibleByCategory.get(key);
4016
5047
  if (!list || list.length === 0) return;
5048
+ const sig = list.map((c) => `${c.id}:${c.assigned_variant_id ?? ""}`).join(",");
5049
+ if (this.filledSlots.has(slot) && this.slotSignatures.get(slot) === sig) return;
5050
+ const oldTimer = this.slotTimers.get(slot);
5051
+ if (oldTimer) {
5052
+ clearInterval(oldTimer);
5053
+ this.slotTimers.delete(slot);
5054
+ }
5055
+ slot.querySelectorAll(
5056
+ ":scope > [data-aegis-rotate-pane], :scope > [data-aegis-slot-rendered], :scope > [data-aegis-rotate-dots]"
5057
+ ).forEach((n) => n.remove());
5058
+ this.slotSignatures.set(slot, sig);
4017
5059
  slot.querySelectorAll(":scope > [data-aegis-slot-default]").forEach((d) => d.remove());
4018
- const rotateMs = parseInt(slot.getAttribute("data-aegis-slot-rotate") || "0", 10);
5060
+ const attrMs = parseInt(slot.getAttribute("data-aegis-slot-rotate") || "0", 10);
5061
+ const declaredMs = list.reduce((m2, c) => {
5062
+ var _a;
5063
+ const v = Number((_a = c.interactive_config) == null ? void 0 : _a.slot_rotate_ms);
5064
+ return Number.isFinite(v) && v > m2 ? v : m2;
5065
+ }, 0);
5066
+ const rotateMs = attrMs > 0 ? attrMs : declaredMs;
5067
+ const flush = slot.hasAttribute("data-aegis-slot-flush");
4019
5068
  if (rotateMs > 0 && list.length > 1) {
4020
- this.renderRotatingSlot(slot, list, rotateMs);
5069
+ this.renderRotatingSlot(slot, list, rotateMs, flush);
4021
5070
  } else {
4022
- this.renderCampaignIntoSlot(list[0], slot);
5071
+ const host = document.createElement("div");
5072
+ host.setAttribute("data-aegis-slot-rendered", list[0].id);
5073
+ slot.appendChild(host);
5074
+ this.renderCampaignIntoSlot(list[0], host);
5075
+ if (flush) this._flattenSlotCard(host);
4023
5076
  }
4024
5077
  this.filledSlots.add(slot);
4025
5078
  });
@@ -4032,7 +5085,7 @@ const _AegisInAppManager = class _AegisInAppManager {
4032
5085
  * is deferred at render and fired per campaign on FIRST visibility, so hidden
4033
5086
  * panes never over-count. One rendered pane → no rotation (just shows it).
4034
5087
  */
4035
- renderRotatingSlot(slot, campaigns, rotateMs) {
5088
+ renderRotatingSlot(slot, campaigns, rotateMs, flush = false) {
4036
5089
  const panes = [];
4037
5090
  for (const c of campaigns) {
4038
5091
  const pane = document.createElement("div");
@@ -4040,8 +5093,10 @@ const _AegisInAppManager = class _AegisInAppManager {
4040
5093
  pane.style.display = "none";
4041
5094
  slot.appendChild(pane);
4042
5095
  this.renderCampaignIntoSlot(c, pane, { deferImpression: true });
4043
- if (pane.childElementCount > 0) panes.push({ el: pane, campaign: c });
4044
- else pane.remove();
5096
+ if (pane.childElementCount > 0) {
5097
+ if (flush) this._flattenSlotCard(pane);
5098
+ panes.push({ el: pane, campaign: c });
5099
+ } else pane.remove();
4045
5100
  }
4046
5101
  if (panes.length === 0) return;
4047
5102
  const impressed = /* @__PURE__ */ new Set();
@@ -4052,17 +5107,59 @@ const _AegisInAppManager = class _AegisInAppManager {
4052
5107
  this.emit("campaign-shown", c);
4053
5108
  };
4054
5109
  let idx = 0;
4055
- panes[0].el.style.display = "";
4056
- markVisible(panes[0].campaign);
4057
- if (panes.length === 1) return;
4058
- const timer = setInterval(() => {
5110
+ const dots = [];
5111
+ if (panes.length > 1) {
5112
+ const bar = document.createElement("div");
5113
+ bar.setAttribute("data-aegis-rotate-dots", "");
5114
+ bar.setAttribute("role", "tablist");
5115
+ bar.setAttribute("aria-label", "Offers");
5116
+ bar.style.cssText = "display:flex;align-items:center;justify-content:center;gap:5px;padding:2px 0 8px;";
5117
+ panes.forEach((_, i) => {
5118
+ const dot = document.createElement("button");
5119
+ dot.type = "button";
5120
+ dot.setAttribute("role", "tab");
5121
+ dot.setAttribute("aria-label", `Offer ${i + 1} of ${panes.length}`);
5122
+ dot.style.cssText = "border:none;padding:0;cursor:pointer;height:6px;width:6px;border-radius:999px;background:rgba(15,23,42,0.2);transition:width .25s ease,background .25s ease;";
5123
+ dot.addEventListener("click", (e) => {
5124
+ e.stopPropagation();
5125
+ show(i, true);
5126
+ });
5127
+ dots.push(dot);
5128
+ bar.appendChild(dot);
5129
+ });
5130
+ slot.appendChild(bar);
5131
+ }
5132
+ const paintDots = () => {
5133
+ dots.forEach((d, i) => {
5134
+ const active = i === idx;
5135
+ d.style.width = active ? "16px" : "6px";
5136
+ d.style.background = active ? "var(--aegis-brand-accent, #4169e1)" : "rgba(15,23,42,0.2)";
5137
+ d.setAttribute("aria-selected", active ? "true" : "false");
5138
+ });
5139
+ };
5140
+ let timer = null;
5141
+ const start = () => setInterval(() => {
5142
+ show(idx + 1);
5143
+ }, rotateMs);
5144
+ function show(i, fromClick = false) {
4059
5145
  panes[idx].el.style.display = "none";
4060
- idx = (idx + 1) % panes.length;
5146
+ idx = (i % panes.length + panes.length) % panes.length;
4061
5147
  panes[idx].el.style.display = "";
4062
5148
  markVisible(panes[idx].campaign);
4063
- }, rotateMs);
5149
+ paintDots();
5150
+ if (fromClick && timer) {
5151
+ clearInterval(timer);
5152
+ timer = start();
5153
+ }
5154
+ }
5155
+ panes[0].el.style.display = "";
5156
+ markVisible(panes[0].campaign);
5157
+ paintDots();
5158
+ if (panes.length === 1) return;
5159
+ timer = start();
5160
+ this.slotTimers.set(slot, timer);
4064
5161
  if (typeof window !== "undefined") {
4065
- window.addEventListener("beforeunload", () => clearInterval(timer), { once: true });
5162
+ window.addEventListener("beforeunload", () => timer && clearInterval(timer), { once: true });
4066
5163
  }
4067
5164
  }
4068
5165
  /**
@@ -4116,6 +5213,14 @@ const _AegisInAppManager = class _AegisInAppManager {
4116
5213
  target.querySelectorAll(":scope > [data-aegis-slot-default]").forEach((d) => d.remove());
4117
5214
  }
4118
5215
  }
5216
+ const spacing = Number(anchor == null ? void 0 : anchor.element_spacing);
5217
+ if (spacing > 0) {
5218
+ if (position === "before" || position === "after") {
5219
+ target.style.margin = `${spacing}px`;
5220
+ } else {
5221
+ target.style.padding = `${spacing}px`;
5222
+ }
5223
+ }
4119
5224
  if (this.filledSlots.has(target)) continue;
4120
5225
  this.renderCampaignIntoSlot(c, target);
4121
5226
  this.filledSlots.add(target);
@@ -4198,6 +5303,13 @@ const _AegisInAppManager = class _AegisInAppManager {
4198
5303
  this.displayedCampaigns.add(campaign.id);
4199
5304
  this.addAnimationStyles();
4200
5305
  const ic = campaign.interactive_config || {};
5306
+ const inlineMul = this._sizeMul(ic);
5307
+ if (inlineMul !== 1) {
5308
+ const sizer = document.createElement("div");
5309
+ sizer.style.cssText = `transform: scale(${inlineMul}); transform-origin: top center;`;
5310
+ target.appendChild(sizer);
5311
+ target = sizer;
5312
+ }
4201
5313
  const { bg, text } = this._surfacePalette(campaign);
4202
5314
  const submitUrl = options == null ? void 0 : options.submitUrl;
4203
5315
  const _formFields = Array.isArray(ic.form_fields) ? ic.form_fields : null;
@@ -4214,70 +5326,81 @@ const _AegisInAppManager = class _AegisInAppManager {
4214
5326
  }
4215
5327
  return;
4216
5328
  }
4217
- switch (campaign.sub_type) {
4218
- case "star_rating":
4219
- rendered = this.renderStarRatingSlot(
4220
- campaign,
4221
- ic,
4222
- bg,
4223
- text,
4224
- target,
4225
- submitUrl
4226
- );
4227
- break;
4228
- case "nps_survey":
4229
- rendered = this.renderNPSSurveySlot(
4230
- campaign,
4231
- ic,
4232
- bg,
4233
- text,
4234
- target,
4235
- submitUrl
4236
- );
4237
- break;
4238
- // Parity tracker — built-in embedded gamification renderers (no
4239
- // AegisMessageRuntime callback needed, so they work on the bill).
4240
- case "spin_wheel":
4241
- rendered = this.renderSpinWheelSlot(campaign, ic, bg, text, target);
4242
- break;
4243
- case "quick_poll":
4244
- rendered = this.renderQuickPollSlot(campaign, ic, bg, text, target);
4245
- break;
4246
- case "carousel_cards":
4247
- rendered = this.renderCarouselCardsSlot(campaign, ic, bg, text, target);
4248
- break;
4249
- case "countdown_offer":
4250
- rendered = this.renderCountdownSlot(campaign, ic, bg, text, target);
4251
- break;
4252
- case "scratch_card":
4253
- rendered = this.renderScratchCardSlot(campaign, ic, bg, text, target);
4254
- break;
4255
- case "custom_html":
4256
- rendered = this.renderCustomHtmlSlot(campaign, ic, bg, text, target);
4257
- break;
4258
- case "stories":
4259
- rendered = renderStoriesRings(this.buildRenderContext(campaign), target);
4260
- break;
4261
- case "video":
4262
- rendered = renderVideoInline(this.buildRenderContext(campaign), target);
4263
- break;
4264
- case "progress_bar":
4265
- rendered = renderProgressBarInline(this.buildRenderContext(campaign), target);
4266
- break;
4267
- case "quiz":
4268
- rendered = this.renderQuizSlot(campaign, ic, bg, text, target);
4269
- if (!rendered) {
5329
+ const _tpl = ic.card_template;
5330
+ const _hasComposedTemplate = !!_tpl && typeof _tpl === "object" && Array.isArray(_tpl.elements);
5331
+ const _isMultiCard = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
5332
+ if (_hasComposedTemplate && !_isMultiCard) {
5333
+ rendered = this.renderComposedCardSlot(campaign, ic, target);
5334
+ } else if (campaign.widget_category === "hero") {
5335
+ rendered = renderHeroInline(this.buildRenderContext(campaign), target);
5336
+ } else
5337
+ switch (campaign.sub_type) {
5338
+ case "star_rating":
5339
+ rendered = this.renderStarRatingSlot(
5340
+ campaign,
5341
+ ic,
5342
+ bg,
5343
+ text,
5344
+ target,
5345
+ submitUrl
5346
+ );
5347
+ break;
5348
+ case "nps_survey":
5349
+ rendered = this.renderNPSSurveySlot(
5350
+ campaign,
5351
+ ic,
5352
+ bg,
5353
+ text,
5354
+ target,
5355
+ submitUrl
5356
+ );
5357
+ break;
5358
+ // Parity tracker — built-in embedded gamification renderers (no
5359
+ // AegisMessageRuntime callback needed, so they work on the bill).
5360
+ case "spin_wheel":
5361
+ rendered = this.renderSpinWheelSlot(campaign, ic, bg, text, target);
5362
+ break;
5363
+ case "quick_poll":
5364
+ rendered = this.renderQuickPollSlot(campaign, ic, bg, text, target);
5365
+ break;
5366
+ case "carousel_cards":
5367
+ rendered = this.renderCarouselCardsSlot(campaign, ic, bg, text, target);
5368
+ break;
5369
+ case "countdown_offer":
5370
+ rendered = this.renderCountdownSlot(campaign, ic, bg, text, target);
5371
+ break;
5372
+ case "scratch_card":
5373
+ rendered = this.renderScratchCardSlot(campaign, ic, bg, text, target);
5374
+ break;
5375
+ case "custom_html":
5376
+ rendered = this.renderCustomHtmlSlot(campaign, ic, bg, text, target);
5377
+ break;
5378
+ case "stories":
5379
+ rendered = renderStoriesRings(this.buildRenderContext(campaign), target);
5380
+ break;
5381
+ case "video":
5382
+ rendered = renderVideoInline(this.buildRenderContext(campaign), target);
5383
+ break;
5384
+ case "progress_bar":
5385
+ rendered = renderProgressBarInline(this.buildRenderContext(campaign), target);
5386
+ break;
5387
+ case "product_recommendation":
5388
+ rendered = renderProductRecommendationInline(this.buildRenderContext(campaign), target);
5389
+ break;
5390
+ case "quiz":
5391
+ rendered = this.renderQuizSlot(campaign, ic, bg, text, target);
5392
+ if (!rendered) {
5393
+ rendered = this.renderGenericCardSlot(campaign, ic, bg, text, target);
5394
+ }
5395
+ break;
5396
+ // Remaining sub_types (sticky_bar, progress_bar,
5397
+ // product_recommendation) render as a generic card on the bill slot
5398
+ // (overlay path handles their full UX); dedicated slot variants get added
5399
+ // here as merchants need them.
5400
+ default:
4270
5401
  rendered = this.renderGenericCardSlot(campaign, ic, bg, text, target);
4271
- }
4272
- break;
4273
- // Remaining sub_types (sticky_bar, progress_bar,
4274
- // product_recommendation) render as a generic card on the bill slot
4275
- // (overlay path handles their full UX); dedicated slot variants get added
4276
- // here as merchants need them.
4277
- default:
4278
- rendered = this.renderGenericCardSlot(campaign, ic, bg, text, target);
4279
- break;
4280
- }
5402
+ break;
5403
+ }
4281
5404
  if (!rendered) return;
4282
5405
  if (!(options == null ? void 0 : options.deferImpression)) {
4283
5406
  this.trackEvent(campaign.id, "impression");
@@ -4326,6 +5449,34 @@ const _AegisInAppManager = class _AegisInAppManager {
4326
5449
  * The catch-all for banner + nudge sub_types (birthday, referral, app-install,
4327
5450
  * win-back, etc.) so EVERY gallery nudge renders on the bill (previously only
4328
5451
  * star_rating / nps_survey / form were handled → nudges never appeared). */
5452
+ /**
5453
+ * Composed single card — renders a campaign's top-level `card_template`
5454
+ * (frame + elements: icon/text/progress/image/cta/group/…) through the generic
5455
+ * `renderCardFromTemplate` engine. The canonical path for the rich card editor:
5456
+ * the storefront value bar, loyalty nudges, and any operator-composed card all
5457
+ * render here from declarative JSON, on ACS / custom / third-party sites.
5458
+ */
5459
+ renderComposedCardSlot(campaign, ic, target) {
5460
+ const c = campaign;
5461
+ const card = {
5462
+ image_url: typeof c.image_url === "string" ? c.image_url : void 0,
5463
+ title: campaign.title,
5464
+ body: campaign.body,
5465
+ cta_text: typeof c.button_text === "string" ? c.button_text : void 0,
5466
+ cta_url: typeof c.action_url === "string" ? c.action_url : void 0,
5467
+ metadata: ic.card_meta || {}
5468
+ };
5469
+ const template = resolveGlobalTemplate(ic);
5470
+ const { accent } = this._surfacePalette(campaign);
5471
+ const tile = renderCardFromTemplate(card, {
5472
+ template,
5473
+ ctx: this.buildRenderContext(campaign),
5474
+ action: "navigate",
5475
+ layoutAccent: this.sanitizeColor(campaign.background_color || accent)
5476
+ });
5477
+ target.appendChild(tile);
5478
+ return true;
5479
+ }
4329
5480
  renderGenericCardSlot(campaign, ic, bg, text, target) {
4330
5481
  const st = this._slotStyle(campaign);
4331
5482
  const card = this._wrapInSlotCard("aegis-in-app-generic-card", campaign.id, bg, text, st.radius);
@@ -4341,6 +5492,45 @@ const _AegisInAppManager = class _AegisInAppManager {
4341
5492
  im.style.cssText = "width: 100%; height: 96px; object-fit: cover; display: block;";
4342
5493
  card.appendChild(im);
4343
5494
  }
5495
+ const widgetCat = typeof c.widget_category === "string" ? c.widget_category : "";
5496
+ if (widgetCat === "promo" && !img) {
5497
+ const row = document.createElement("div");
5498
+ row.style.cssText = `display:flex; align-items:center; gap:12px; min-height:46px; padding:${st.padY ?? 10}px ${st.padX ?? 14}px;`;
5499
+ const colL = document.createElement("div");
5500
+ colL.style.cssText = "flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px;";
5501
+ if (campaign.title) {
5502
+ const t = document.createElement("div");
5503
+ t.style.cssText = `font-size:13px; font-weight:${st.weight ?? "700"}; line-height:1.25; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;`;
5504
+ t.textContent = campaign.title;
5505
+ colL.appendChild(t);
5506
+ }
5507
+ if (campaign.body) {
5508
+ const b = document.createElement("div");
5509
+ b.style.cssText = "font-size:11.5px; opacity:0.7; line-height:1.3; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;";
5510
+ b.textContent = campaign.body;
5511
+ colL.appendChild(b);
5512
+ }
5513
+ row.appendChild(colL);
5514
+ if (buttonText) {
5515
+ const btn = document.createElement("button");
5516
+ btn.type = "button";
5517
+ btn.textContent = buttonText;
5518
+ const accentTok = `var(--aegis-brand-accent, ${text})`;
5519
+ btn.style.cssText = `flex:0 0 auto; align-self:center; cursor:pointer; white-space:nowrap;background:color-mix(in srgb, ${accentTok} 12%, #fff); color:${accentTok}; border:none;border-radius:999px; padding:7px 16px; font-size:12.5px; font-weight:700;`;
5520
+ btn.addEventListener("click", () => {
5521
+ void this.trackEvent(campaign.id, "clicked");
5522
+ if (actionUrl) {
5523
+ window.open(actionUrl, "_blank", "noopener");
5524
+ } else if (typeof window !== "undefined") {
5525
+ window.dispatchEvent(new CustomEvent("aegis:open-login", { detail: { campaign_id: campaign.id } }));
5526
+ }
5527
+ });
5528
+ row.appendChild(btn);
5529
+ }
5530
+ card.appendChild(row);
5531
+ target.appendChild(card);
5532
+ return true;
5533
+ }
4344
5534
  const cbody = document.createElement("div");
4345
5535
  cbody.style.cssText = `padding: ${st.padY ?? 12}px ${st.padX ?? 12}px; display: flex; flex-direction: column; gap: 6px;`;
4346
5536
  if (campaign.title) {
@@ -4484,8 +5674,14 @@ const _AegisInAppManager = class _AegisInAppManager {
4484
5674
  body.style.alignItems = "center";
4485
5675
  body.style.justifyContent = "center";
4486
5676
  const done = document.createElement("div");
4487
- done.style.cssText = "padding: 8px 0; font-size: 13px; font-weight: 600; text-align: center;";
4488
- done.textContent = typeof ((_a = campaign.interactive_config) == null ? void 0 : _a.thank_you_message) === "string" ? campaign.interactive_config.thank_you_message : "✓ Thanks — your details are saved.";
5677
+ done.style.cssText = "padding: 8px 0; font-size: 13px; font-weight: 600; text-align: center; display: inline-flex; align-items: center; justify-content: center; gap: 6px;";
5678
+ const customThanks = typeof ((_a = campaign.interactive_config) == null ? void 0 : _a.thank_you_message) === "string" ? campaign.interactive_config.thank_you_message : null;
5679
+ if (customThanks) {
5680
+ done.textContent = customThanks;
5681
+ } else {
5682
+ done.innerHTML = lucideSvg("check", { size: 14 });
5683
+ done.appendChild(document.createTextNode("Thanks — your details are saved."));
5684
+ }
4489
5685
  body.appendChild(done);
4490
5686
  this._playReaction(body, "affirm", "check.json", campaign.interactive_config);
4491
5687
  });
@@ -4618,6 +5814,8 @@ const _AegisInAppManager = class _AegisInAppManager {
4618
5814
  const hubImg = cfgStr("spin_hub_image_url");
4619
5815
  const wheelBgImg = cfgStr("wheel_background_url");
4620
5816
  const pointerImg = cfgStr("pointer_image_url");
5817
+ const pointerCenter = (cfgStr("spin_pointer_position") || "top") === "center";
5818
+ const pointerBase = pointerCenter ? "translate(-50%, -100%)" : "translateX(-50%)";
4621
5819
  const isUrl = (s) => /^https?:\/\//i.test(s);
4622
5820
  const now = () => typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
4623
5821
  let rotation = 0;
@@ -4677,7 +5875,7 @@ const _AegisInAppManager = class _AegisInAppManager {
4677
5875
  vibrate(18);
4678
5876
  };
4679
5877
  const body = document.createElement("div");
4680
- body.style.cssText = `padding: ${st.padY ?? 22}px ${st.padX ?? 20}px 20px; display: flex; flex-direction: column; align-items: center; gap: 16px;`;
5878
+ body.style.cssText = `padding: ${st.padY ?? 22}px ${st.padX ?? 20}px 20px; display: flex; flex-direction: column; align-items: center; gap: 10px;`;
4681
5879
  if (campaign.title) {
4682
5880
  const t = document.createElement("div");
4683
5881
  t.style.cssText = `font-size: 18px; font-weight: 800; letter-spacing: -0.01em; text-align: center; font-family: 'Inter Tight', Inter, system-ui, -apple-system, sans-serif;`;
@@ -4695,11 +5893,55 @@ const _AegisInAppManager = class _AegisInAppManager {
4695
5893
  const wheelWrap = document.createElement("div");
4696
5894
  wheelWrap.style.cssText = `position: relative; width: ${SIZE}px; height: ${SIZE}px; filter: drop-shadow(0 10px 22px rgba(0,0,0,0.30));`;
4697
5895
  const pointer = document.createElement("div");
4698
- pointer.style.cssText = pointerImg ? `position: absolute; left: 50%; top: -10px; transform: translateX(-50%); transform-origin: 50% 0; width: 26px; height: 30px; background: center / contain no-repeat url("${pointerImg}"); z-index: 5; filter: drop-shadow(0 2px 2px rgba(0,0,0,0.35));` : `position: absolute; left: 50%; top: -7px; transform: translateX(-50%); transform-origin: 50% 0; width: 0; height: 0; border-left: 9px solid transparent; border-right: 9px solid transparent; border-top: 16px solid ${pointerColor}; z-index: 5; filter: drop-shadow(0 2px 2px rgba(0,0,0,0.35));`;
5896
+ if (pointerCenter) {
5897
+ const needleH = Math.round(radius * 0.42);
5898
+ pointer.style.cssText = `position: absolute; left: 50%; top: 50%; width: 20px; height: ${needleH}px; transform: ${pointerBase}; transform-origin: 50% 100%; z-index: 6; pointer-events: none;`;
5899
+ if (pointerImg) {
5900
+ const pim = document.createElement("img");
5901
+ pim.src = pointerImg;
5902
+ pim.alt = "";
5903
+ pim.style.cssText = "width: 100%; height: 100%; object-fit: contain; object-position: top; display: block; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4));";
5904
+ pointer.appendChild(pim);
5905
+ } else {
5906
+ const stem = document.createElement("div");
5907
+ stem.style.cssText = `position: absolute; left: 50%; bottom: 0; transform: translateX(-50%); width: 4px; height: 100%; background: ${pointerColor}; border-radius: 2px; box-shadow: 0 1px 2px rgba(0,0,0,0.35);`;
5908
+ const head = document.createElement("div");
5909
+ head.style.cssText = `position: absolute; left: 50%; top: -1px; transform: translateX(-50%); width: 0; height: 0; border-left: 8px solid transparent; border-right: 8px solid transparent; border-bottom: 14px solid ${pointerColor}; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.4));`;
5910
+ pointer.appendChild(stem);
5911
+ pointer.appendChild(head);
5912
+ }
5913
+ } else if (pointerImg) {
5914
+ pointer.style.cssText = `position: absolute; left: 50%; top: -12px; transform: ${pointerBase}; transform-origin: 50% 0; width: 30px; height: 34px; z-index: 5;`;
5915
+ const pim = document.createElement("img");
5916
+ pim.src = pointerImg;
5917
+ pim.alt = "";
5918
+ pim.style.cssText = "width: 100%; height: 100%; object-fit: contain; display: block; filter: drop-shadow(0 2px 2px rgba(0,0,0,0.35));";
5919
+ pointer.appendChild(pim);
5920
+ } else {
5921
+ pointer.style.cssText = `position: absolute; left: 50%; top: -7px; transform: ${pointerBase}; transform-origin: 50% 0; width: 0; height: 0; border-left: 9px solid transparent; border-right: 9px solid transparent; border-top: 16px solid ${pointerColor}; z-index: 5; filter: drop-shadow(0 2px 2px rgba(0,0,0,0.35));`;
5922
+ }
4699
5923
  const wheel = document.createElement("div");
4700
5924
  const wheelFace = wheelBgImg ? `center / cover no-repeat url("${wheelBgImg}")` : `conic-gradient(${stops})`;
4701
- wheel.style.cssText = `position: relative; width: ${SIZE}px; height: ${SIZE}px; border-radius: 50%; border: 5px solid #ffffff; box-shadow: 0 0 0 2px ${text}2e, inset 0 0 22px rgba(0,0,0,0.20); background: ${wheelFace}; overflow: hidden; will-change: transform; touch-action: none; cursor: ${spinMode === "button" ? "default" : "grab"};`;
5925
+ const wheelRim = wheelBgImg ? "0" : "5px solid #ffffff";
5926
+ const wheelShadow = wheelBgImg ? `0 0 0 2px ${text}2e` : `0 0 0 2px ${text}2e, inset 0 0 22px rgba(0,0,0,0.20)`;
5927
+ wheel.style.cssText = `position: relative; width: ${SIZE}px; height: ${SIZE}px; border-radius: 50%; border: ${wheelRim}; box-shadow: ${wheelShadow}; background: ${wheelFace}; overflow: hidden; will-change: transform; touch-action: none; cursor: ${spinMode === "button" ? "default" : "grab"};`;
4702
5928
  if (idleAnim === "wobble") wheel.style.animation = "aegisSpinWobble 3s ease-in-out infinite";
5929
+ const sliceImgFrac = Math.max(40, Math.min(100, Number(ic.spin_slice_image_size) || 100)) / 100;
5930
+ if (!wheelBgImg) shown.forEach((s, i) => {
5931
+ const simg = typeof s.image_url === "string" ? s.image_url : "";
5932
+ if (!simg || !isUrl(simg)) return;
5933
+ const pts = ["50% 50%"];
5934
+ const steps = Math.max(2, Math.ceil(spans[i] / 6));
5935
+ for (let st2 = 0; st2 <= steps; st2++) {
5936
+ const a = (starts[i] + spans[i] * st2 / steps) * Math.PI / 180;
5937
+ const x = 50 + 50 * sliceImgFrac * Math.sin(a);
5938
+ const y = 50 - 50 * sliceImgFrac * Math.cos(a);
5939
+ pts.push(`${x.toFixed(2)}% ${y.toFixed(2)}%`);
5940
+ }
5941
+ const segEl = document.createElement("div");
5942
+ segEl.style.cssText = `position: absolute; inset: 0; background: center / cover no-repeat url("${simg}"); clip-path: polygon(${pts.join(",")}); pointer-events: none;`;
5943
+ wheel.appendChild(segEl);
5944
+ });
4703
5945
  if (!wheelBgImg) starts.forEach((b) => {
4704
5946
  const ln = document.createElement("div");
4705
5947
  ln.style.cssText = `position: absolute; left: 50%; top: 0; width: 1.5px; height: 50%; background: rgba(255,255,255,0.55); transform-origin: bottom center; transform: translateX(-50%) rotate(${b}deg); pointer-events: none;`;
@@ -4711,17 +5953,6 @@ const _AegisInAppManager = class _AegisInAppManager {
4711
5953
  const rText = (rHub + rRim) / 2;
4712
5954
  if (!wheelBgImg) labels.forEach((label, i) => {
4713
5955
  const mid = midOf(i);
4714
- const wedgeImg = shown[i] && typeof shown[i].image_url === "string" ? shown[i].image_url : "";
4715
- if (wedgeImg && isUrl(wedgeImg)) {
4716
- const flipImg = mid > 180 && mid < 360;
4717
- const isz = Math.max(18, Math.min(40, bandLen * 0.62));
4718
- const wim = document.createElement("img");
4719
- wim.src = wedgeImg;
4720
- wim.alt = label || "";
4721
- wim.style.cssText = `position: absolute; left: 50%; top: 50%; width: ${isz}px; height: ${isz}px; object-fit: contain; transform: translate(-50%, -50%) rotate(${mid}deg) translateY(-${rText}px) rotate(${flipImg ? 90 : -90}deg); filter: drop-shadow(0 1px 3px rgba(0,0,0,0.55)); pointer-events: none;`;
4722
- wheel.appendChild(wim);
4723
- return;
4724
- }
4725
5956
  const icoVal = shown[i] && typeof shown[i].icon === "string" ? shown[i].icon : "";
4726
5957
  const chord = 2 * rText * Math.sin(spans[i] * Math.PI / 180 / 2);
4727
5958
  const byThickness = chord * 0.6;
@@ -4765,28 +5996,13 @@ const _AegisInAppManager = class _AegisInAppManager {
4765
5996
  const hub = document.createElement("div");
4766
5997
  hub.style.cssText = `position: absolute; left: 50%; top: 50%; width: 40px; height: 40px; transform: translate(-50%, -50%); border-radius: 50%; background: radial-gradient(circle at 35% 30%, #ffffff, #e9edf5); border: 3px solid #fff; box-shadow: 0 3px 8px rgba(0,0,0,0.30); z-index: 4; overflow: hidden; display: flex; align-items: center; justify-content: center;`;
4767
5998
  if (hubImg) {
4768
- const hc = document.createElement("canvas");
4769
- hc.width = 40;
4770
- hc.height = 40;
4771
- hc.style.cssText = "width: 100%; height: 100%;";
4772
- const hcx = hc.getContext("2d");
4773
- const him = new Image();
4774
- him.crossOrigin = "anonymous";
4775
- him.onload = () => {
4776
- if (!hcx) return;
4777
- const ar = him.width / him.height || 1;
4778
- let w = 32, h = 32;
4779
- if (ar > 1) h = 32 / ar;
4780
- else w = 32 * ar;
4781
- hcx.drawImage(him, (40 - w) / 2, (40 - h) / 2, w, h);
4782
- hcx.globalCompositeOperation = "source-in";
4783
- hcx.fillStyle = "#5b626e";
4784
- hcx.fillRect(0, 0, 40, 40);
4785
- };
4786
- him.onerror = () => {
4787
- };
5999
+ hub.style.border = "none";
6000
+ hub.style.background = "transparent";
6001
+ const him = document.createElement("img");
4788
6002
  him.src = hubImg;
4789
- hub.appendChild(hc);
6003
+ him.alt = "";
6004
+ him.style.cssText = "width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block;";
6005
+ hub.appendChild(him);
4790
6006
  } else {
4791
6007
  const hubDot = document.createElement("div");
4792
6008
  hubDot.style.cssText = `width: 11px; height: 11px; border-radius: 50%; background: ${bg};`;
@@ -4800,22 +6016,22 @@ const _AegisInAppManager = class _AegisInAppManager {
4800
6016
  body.appendChild(wheelWrap);
4801
6017
  if (!card.style.position) card.style.position = "relative";
4802
6018
  const result = document.createElement("div");
4803
- result.style.cssText = "font-size: 14px; font-weight: 800; min-height: 20px; text-align: center; letter-spacing: 0.2px;";
6019
+ result.style.cssText = "font-size: 14px; font-weight: 800; min-height: 0; text-align: center; letter-spacing: 0.2px;";
4804
6020
  if (ic.spin_sound_enabled !== false) {
4805
6021
  const soundBtn = document.createElement("button");
4806
6022
  soundBtn.type = "button";
4807
6023
  soundBtn.setAttribute("aria-label", "Toggle sound");
4808
- soundBtn.textContent = soundOn ? "🔊" : "🔇";
6024
+ soundBtn.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
4809
6025
  soundBtn.style.cssText = "position: absolute; top: 10px; left: 12px; background: transparent; border: none; font-size: 15px; cursor: pointer; opacity: 0.65; z-index: 6; line-height: 1;";
4810
6026
  soundBtn.addEventListener("click", () => {
4811
6027
  soundOn = !soundOn;
4812
- soundBtn.textContent = soundOn ? "🔊" : "🔇";
6028
+ soundBtn.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
4813
6029
  });
4814
6030
  card.appendChild(soundBtn);
4815
6031
  }
4816
6032
  const btn = document.createElement("button");
4817
6033
  btn.textContent = campaign.button_text || "Spin the wheel";
4818
- btn.style.cssText = `padding: 12px 32px; border-radius: 999px; border: none; background: #ffffff; color: ${bg}; font-size: 15px; font-weight: 800; letter-spacing: 0.2px; cursor: pointer; box-shadow: 0 6px 16px rgba(0,0,0,0.22); transition: transform 0.15s, box-shadow 0.15s; font-family: 'Inter Tight', Inter, system-ui, -apple-system, sans-serif;`;
6034
+ btn.style.cssText = `padding: 9px 22px; border-radius: 999px; border: none; background: ${text}; color: ${bg}; font-size: 13px; font-weight: 700; letter-spacing: 0.2px; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.20); transition: transform 0.15s, box-shadow 0.15s; font-family: 'Inter Tight', Inter, system-ui, -apple-system, sans-serif;`;
4819
6035
  btn.addEventListener("mouseenter", () => {
4820
6036
  if (btn.disabled) return;
4821
6037
  btn.style.transform = "translateY(-1px)";
@@ -4844,12 +6060,12 @@ const _AegisInAppManager = class _AegisInAppManager {
4844
6060
  pVel *= 0.7;
4845
6061
  pDefl += pVel;
4846
6062
  if (Math.abs(pDefl) > 0.06 || Math.abs(pVel) > 0.06) {
4847
- pointer.style.transform = `translateX(-50%) rotate(${pDefl.toFixed(2)}deg)`;
6063
+ pointer.style.transform = `${pointerBase} rotate(${pDefl.toFixed(2)}deg)`;
4848
6064
  pointerRaf = requestAnimationFrame(stepPointer);
4849
6065
  } else {
4850
6066
  pDefl = 0;
4851
6067
  pVel = 0;
4852
- pointer.style.transform = "translateX(-50%) rotate(0deg)";
6068
+ pointer.style.transform = `${pointerBase} rotate(0deg)`;
4853
6069
  pointerRaf = 0;
4854
6070
  }
4855
6071
  };
@@ -4924,7 +6140,7 @@ const _AegisInAppManager = class _AegisInAppManager {
4924
6140
  var _a;
4925
6141
  try {
4926
6142
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(r.code));
4927
- chip.textContent = "Copied";
6143
+ chip.textContent = "Copied";
4928
6144
  } catch {
4929
6145
  }
4930
6146
  });
@@ -4970,7 +6186,7 @@ const _AegisInAppManager = class _AegisInAppManager {
4970
6186
  var _a;
4971
6187
  try {
4972
6188
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(r.code));
4973
- chip.textContent = "Copied";
6189
+ chip.textContent = "Copied";
4974
6190
  } catch {
4975
6191
  }
4976
6192
  });
@@ -5211,7 +6427,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5211
6427
  pctLabel.textContent = `${pct}%`;
5212
6428
  pctLabel.style.opacity = "0.8";
5213
6429
  } else if (pctLabel && i === selected) {
5214
- pctLabel.textContent = "";
6430
+ pctLabel.innerHTML = lucideSvg("check", { size: 14 });
5215
6431
  pctLabel.style.opacity = "0.8";
5216
6432
  }
5217
6433
  });
@@ -5234,59 +6450,18 @@ const _AegisInAppManager = class _AegisInAppManager {
5234
6450
  }
5235
6451
  const strip = document.createElement("div");
5236
6452
  strip.style.cssText = `display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory; padding: ${st.padY ?? 12}px ${st.padX ?? 12}px;`;
6453
+ const template = resolveGlobalTemplate(ic);
6454
+ const ctx = this.buildRenderContext(campaign);
6455
+ const accent = this.sanitizeColor(campaign.background_color || text);
5237
6456
  cards.forEach((c, i) => {
5238
- const tile = document.createElement("div");
5239
- const ctaUrl = typeof c.cta_url === "string" ? c.cta_url : "";
5240
- 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"};`;
5241
- const videoUrl = typeof c.video_url === "string" ? c.video_url : "";
5242
- const imageUrl = typeof c.image_url === "string" ? c.image_url : "";
5243
- if (videoUrl) {
5244
- const v = document.createElement("video");
5245
- v.src = videoUrl;
5246
- v.muted = true;
5247
- v.loop = true;
5248
- v.autoplay = true;
5249
- v.playsInline = true;
5250
- v.setAttribute("playsinline", "");
5251
- v.style.cssText = "width: 100%; height: 90px; object-fit: cover; display: block;";
5252
- tile.appendChild(v);
5253
- } else if (imageUrl) {
5254
- const im = document.createElement("img");
5255
- im.src = imageUrl;
5256
- im.alt = "";
5257
- im.loading = "lazy";
5258
- im.style.cssText = "width: 100%; height: 90px; object-fit: cover; display: block;";
5259
- tile.appendChild(im);
5260
- }
5261
- const tb = document.createElement("div");
5262
- tb.style.cssText = "padding: 0 8px 8px; display: flex; flex-direction: column; gap: 4px;";
5263
- if (typeof c.title === "string" && c.title) {
5264
- const tt = document.createElement("div");
5265
- tt.style.cssText = "font-size: 12px; font-weight: 600; line-height: 1.3;";
5266
- tt.textContent = c.title;
5267
- tb.appendChild(tt);
5268
- }
5269
- if (typeof c.body === "string" && c.body) {
5270
- const tbd = document.createElement("div");
5271
- tbd.style.cssText = "font-size: 10.5px; opacity: 0.72; line-height: 1.3;";
5272
- tbd.textContent = c.body;
5273
- tb.appendChild(tbd);
5274
- }
5275
- const ctaText = typeof c.cta_text === "string" ? c.cta_text : "";
5276
- if (ctaText && ctaUrl) {
5277
- const cta = document.createElement("button");
5278
- cta.textContent = ctaText;
5279
- 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;`;
5280
- const goto = (e) => {
5281
- e.stopPropagation();
5282
- void this.trackEvent(campaign.id, "clicked", { stepId: `card_${i}` });
5283
- window.open(ctaUrl, "_blank", "noopener");
5284
- };
5285
- cta.addEventListener("click", goto);
5286
- tile.addEventListener("click", goto);
5287
- tb.appendChild(cta);
5288
- }
5289
- tile.appendChild(tb);
6457
+ const tile = renderCardFromTemplate(c, {
6458
+ template,
6459
+ ctx,
6460
+ action: "navigate",
6461
+ layout: "row",
6462
+ layoutAccent: accent
6463
+ });
6464
+ tile.setAttribute("data-card-index", String(i));
5290
6465
  strip.appendChild(tile);
5291
6466
  });
5292
6467
  card.appendChild(strip);
@@ -5383,6 +6558,34 @@ const _AegisInAppManager = class _AegisInAppManager {
5383
6558
  target.appendChild(card);
5384
6559
  return true;
5385
6560
  }
6561
+ /** Foil palette — a brushed-metal gradient + a contrast "ink" (for the
6562
+ * SCRATCH-HERE text, sparkle motif, and logo tint) derived from the operator's
6563
+ * chosen foil colour (brand primary / custom hex). Unset / invalid → the
6564
+ * default silver. Light foils get dark ink, dark foils get white ink, so the
6565
+ * prompt stays legible on any colour. */
6566
+ _scratchFoilPalette(hex) {
6567
+ const clean = (hex || "").trim().replace(/^#/, "");
6568
+ if (!/^[0-9a-fA-F]{6}$/.test(clean)) {
6569
+ return { g0: "#d7dbe2", g1: "#b4bac4", g2: "#c9cdd6", ink: "#6b7280" };
6570
+ }
6571
+ const r = parseInt(clean.slice(0, 2), 16);
6572
+ const g = parseInt(clean.slice(2, 4), 16);
6573
+ const b = parseInt(clean.slice(4, 6), 16);
6574
+ const toward = (amt) => {
6575
+ const m2 = (c) => Math.round(c + (255 - c) * amt);
6576
+ return `rgb(${m2(r)}, ${m2(g)}, ${m2(b)})`;
6577
+ };
6578
+ const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
6579
+ return {
6580
+ g0: toward(0.34),
6581
+ // bright sheen
6582
+ g1: `#${clean}`,
6583
+ // base colour
6584
+ g2: toward(0.18),
6585
+ // soft sheen
6586
+ ink: lum > 0.62 ? "#5b6270" : "rgba(255,255,255,0.92)"
6587
+ };
6588
+ }
5386
6589
  /** Built-in embedded SCRATCH CARD — a canvas foil the customer scratches to
5387
6590
  * reveal the server-picked prize, then submits (records + grants). Confetti
5388
6591
  * on reveal. No AegisMessageRuntime callback dependency (works on the bill). */
@@ -5400,6 +6603,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5400
6603
  let soundOn = ic.scratch_sound_enabled !== false;
5401
6604
  const revealImg = cfgStr("reveal_image_url");
5402
6605
  const foilImg = cfgStr("scratch_foil_image_url");
6606
+ const foilPal = this._scratchFoilPalette(cfgStr("scratch_foil_color"));
5403
6607
  const pool = Array.isArray(ic.prize_pool) ? ic.prize_pool : [];
5404
6608
  const isUrl = (s) => /^https?:\/\//i.test(s);
5405
6609
  const sampleWin = pool.find(
@@ -5509,11 +6713,11 @@ const _AegisInAppManager = class _AegisInAppManager {
5509
6713
  const sb = document.createElement("button");
5510
6714
  sb.type = "button";
5511
6715
  sb.setAttribute("aria-label", "Toggle sound");
5512
- sb.textContent = soundOn ? "🔊" : "🔇";
6716
+ sb.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
5513
6717
  sb.style.cssText = "position: absolute; top: 10px; left: 12px; background: transparent; border: none; font-size: 15px; cursor: pointer; opacity: 0.65; z-index: 6; line-height: 1;";
5514
6718
  sb.addEventListener("click", () => {
5515
6719
  soundOn = !soundOn;
5516
- sb.textContent = soundOn ? "🔊" : "🔇";
6720
+ sb.innerHTML = lucideSvg(soundOn ? "volume-2" : "volume-x", { size: 15 });
5517
6721
  });
5518
6722
  card.appendChild(sb);
5519
6723
  }
@@ -5542,9 +6746,9 @@ const _AegisInAppManager = class _AegisInAppManager {
5542
6746
  if (!ctx) return;
5543
6747
  ctx.globalCompositeOperation = "source-over";
5544
6748
  const foil = ctx.createLinearGradient(0, 0, CW, CH);
5545
- foil.addColorStop(0, "#d7dbe2");
5546
- foil.addColorStop(0.5, "#b4bac4");
5547
- foil.addColorStop(1, "#c9cdd6");
6749
+ foil.addColorStop(0, foilPal.g0);
6750
+ foil.addColorStop(0.5, foilPal.g1);
6751
+ foil.addColorStop(1, foilPal.g2);
5548
6752
  ctx.fillStyle = foil;
5549
6753
  ctx.fillRect(0, 0, CW, CH);
5550
6754
  ctx.textAlign = "center";
@@ -5553,17 +6757,19 @@ const _AegisInAppManager = class _AegisInAppManager {
5553
6757
  ctx.globalAlpha = 0.6;
5554
6758
  ctx.drawImage(logo, (CW - logo.width) / 2, CH * 0.42 - logo.height / 2, logo.width, logo.height);
5555
6759
  ctx.globalAlpha = 1;
5556
- ctx.fillStyle = "#6b7280";
6760
+ ctx.fillStyle = foilPal.ink;
5557
6761
  ctx.font = "700 12px system-ui, sans-serif";
5558
6762
  ctx.fillText("SCRATCH HERE", CW / 2, CH * 0.84);
5559
6763
  } else {
5560
- ctx.fillStyle = "rgba(108,116,128,0.18)";
6764
+ ctx.globalAlpha = 0.22;
6765
+ ctx.fillStyle = foilPal.ink;
5561
6766
  ctx.font = "12px system-ui, sans-serif";
5562
6767
  for (let gy = 18; gy < CH; gy += 30) {
5563
6768
  const off = (gy / 30 | 0) % 2 ? 16 : 0;
5564
6769
  for (let gx = 16 + off; gx < CW; gx += 32) ctx.fillText("✦", gx, gy);
5565
6770
  }
5566
- ctx.fillStyle = "#7c828e";
6771
+ ctx.globalAlpha = 1;
6772
+ ctx.fillStyle = foilPal.ink;
5567
6773
  ctx.font = "700 13px system-ui, sans-serif";
5568
6774
  ctx.fillText("SCRATCH HERE", CW / 2, CH / 2);
5569
6775
  }
@@ -5590,7 +6796,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5590
6796
  if (!lctx) return;
5591
6797
  lctx.drawImage(im, 0, 0, lc.width, lc.height);
5592
6798
  lctx.globalCompositeOperation = "source-in";
5593
- lctx.fillStyle = "#5b626e";
6799
+ lctx.fillStyle = foilPal.ink;
5594
6800
  lctx.fillRect(0, 0, lc.width, lc.height);
5595
6801
  paintFoil(lc);
5596
6802
  };
@@ -5680,7 +6886,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5680
6886
  var _a;
5681
6887
  try {
5682
6888
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(code));
5683
- chip.textContent = "Copied";
6889
+ chip.textContent = "Copied";
5684
6890
  } catch {
5685
6891
  }
5686
6892
  });
@@ -5868,6 +7074,19 @@ const _AegisInAppManager = class _AegisInAppManager {
5868
7074
  `;
5869
7075
  return card;
5870
7076
  }
7077
+ /**
7078
+ * Flush a rendered slot card into its host card — drop the per-campaign radius
7079
+ * + shadow so it reads as one unit with the host wrapper (used for the
7080
+ * storefront value bar via `data-aegis-slot-flush`). Targets every
7081
+ * `[data-campaign-id]` card inside `container` (or the container itself).
7082
+ */
7083
+ _flattenSlotCard(container) {
7084
+ const cards = container.matches("[data-campaign-id]") ? [container] : Array.from(container.querySelectorAll("[data-campaign-id]"));
7085
+ for (const el of cards) {
7086
+ el.style.borderRadius = "0";
7087
+ el.style.boxShadow = "none";
7088
+ }
7089
+ }
5871
7090
  // Optional header/brand image atop a survey-style widget (star / nps).
5872
7091
  _appendHeaderImage(body, ic) {
5873
7092
  const url = ic.header_image_url;
@@ -6309,12 +7528,18 @@ const _AegisInAppManager = class _AegisInAppManager {
6309
7528
  }
6310
7529
  }
6311
7530
  displayCampaign(campaign) {
7531
+ if (Array.isArray(campaign.delivery_modes) && campaign.delivery_modes.length > 0 && !campaign.delivery_modes.includes("in_app_overlay")) {
7532
+ return;
7533
+ }
6312
7534
  const proceed = this.emit("campaign-will-show", campaign);
6313
7535
  if (!proceed) {
6314
7536
  this.log(`campaign ${campaign.id} suppressed by campaign-will-show handler`);
6315
7537
  return;
6316
7538
  }
6317
7539
  this.displayedCampaigns.add(campaign.id);
7540
+ this._displaySizeMul = this._sizeMul(
7541
+ campaign.interactive_config
7542
+ );
6318
7543
  const interactiveSubTypes = /* @__PURE__ */ new Set([
6319
7544
  "spin_wheel",
6320
7545
  "scratch_card",
@@ -6435,6 +7660,16 @@ const _AegisInAppManager = class _AegisInAppManager {
6435
7660
  return {
6436
7661
  campaign,
6437
7662
  getCartState: this.getCartState,
7663
+ properties: () => {
7664
+ var _a, _b;
7665
+ const cart = (_a = this.getCartState) == null ? void 0 : _a.call(this);
7666
+ const ctxObj = ((_b = campaign.interactive_config) == null ? void 0 : _b.card_context) || {};
7667
+ return {
7668
+ "cart.total": cart == null ? void 0 : cart.total,
7669
+ "cart.count": cart == null ? void 0 : cart.itemCount,
7670
+ ...ctxObj
7671
+ };
7672
+ },
6438
7673
  trackEvent: (id, evt, extra) => {
6439
7674
  void this.trackEvent(id, evt, extra);
6440
7675
  },
@@ -6507,6 +7742,14 @@ const _AegisInAppManager = class _AegisInAppManager {
6507
7742
  * slot renderers (`renderSpinWheelSlot` / `renderScratchCardSlot`) in a modal
6508
7743
  * so the gamified widget is fully playable — not a dead frame.
6509
7744
  */
7745
+ /** Display-size multiplier — operator control over how big a bounded overlay
7746
+ * renders (small 0.82 / medium 1 / large 1.18). Default medium = prior size.
7747
+ * Read from interactive_config.display_size; mirrors the schema enum + the
7748
+ * dashboard preview harness so the operator's choice is honored identically. */
7749
+ _sizeMul(ic) {
7750
+ const s = ic && typeof ic.display_size === "string" ? ic.display_size : "medium";
7751
+ return s === "small" ? 0.82 : s === "large" ? 1.18 : 1;
7752
+ }
6510
7753
  renderGamificationOverlay(campaign, ic, bg, text) {
6511
7754
  const overlay = this.createOverlay(`aegis-in-app-${campaign.sub_type}-overlay`);
6512
7755
  const modal = document.createElement("div");
@@ -6521,7 +7764,11 @@ const _AegisInAppManager = class _AegisInAppManager {
6521
7764
  this.renderSpinWheelSlot(campaign, ic, bg, text, modal);
6522
7765
  }
6523
7766
  this._addCornerClose(modal, overlay, campaign.id, text);
6524
- overlay.appendChild(modal);
7767
+ const mul = this._sizeMul(ic);
7768
+ const sizer = document.createElement("div");
7769
+ sizer.style.cssText = `display: flex; justify-content: center; align-items: center; width: 100%; transform: scale(${mul}); transform-origin: center;`;
7770
+ sizer.appendChild(modal);
7771
+ overlay.appendChild(sizer);
6525
7772
  this.addAnimationStyles();
6526
7773
  document.body.appendChild(overlay);
6527
7774
  }
@@ -6533,7 +7780,7 @@ const _AegisInAppManager = class _AegisInAppManager {
6533
7780
  '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>'
6534
7781
  );
6535
7782
  if (closeIcon) x.appendChild(closeIcon);
6536
- else x.textContent = "";
7783
+ else x.innerHTML = lucideSvg("x", { size: 14 });
6537
7784
  x.style.cssText = `
6538
7785
  position: absolute; top: 12px; right: 12px; z-index: 4; width: 28px; height: 28px;
6539
7786
  display: flex; align-items: center; justify-content: center; border: none;
@@ -7184,8 +8431,30 @@ const _AegisInAppManager = class _AegisInAppManager {
7184
8431
  background: rgba(0,0,0,0.5); display: flex; align-items: center;
7185
8432
  justify-content: center; z-index: 99999; animation: aegisFadeIn 0.3s ease;
7186
8433
  `;
8434
+ if (this._displaySizeMul !== 1) {
8435
+ const mul = this._displaySizeMul;
8436
+ queueMicrotask(() => {
8437
+ const card = overlay.firstElementChild;
8438
+ if (!card || card.dataset.aegisSized) return;
8439
+ card.dataset.aegisSized = "1";
8440
+ const wrap = document.createElement("div");
8441
+ wrap.style.cssText = `display: flex; align-items: center; justify-content: center; transform: scale(${mul}); transform-origin: center;`;
8442
+ overlay.insertBefore(wrap, card);
8443
+ wrap.appendChild(card);
8444
+ });
8445
+ }
7187
8446
  return overlay;
7188
8447
  }
8448
+ /** Wrap a card in a display-size scaling container (no-op at medium/×1). Used
8449
+ * by formats that build their overlay inline (renderModal) rather than via
8450
+ * createOverlay, so size is honored identically. */
8451
+ _wrapScaled(card) {
8452
+ if (this._displaySizeMul === 1) return card;
8453
+ const wrap = document.createElement("div");
8454
+ wrap.style.cssText = `display: flex; align-items: center; justify-content: center; transform: scale(${this._displaySizeMul}); transform-origin: center;`;
8455
+ wrap.appendChild(card);
8456
+ return wrap;
8457
+ }
7189
8458
  createCTAButton(campaign, bg, text) {
7190
8459
  const btn = document.createElement("button");
7191
8460
  btn.className = "aegis-cta";
@@ -7349,12 +8618,17 @@ const _AegisInAppManager = class _AegisInAppManager {
7349
8618
  _surfacePalette(campaign) {
7350
8619
  const ic = campaign.interactive_config || {};
7351
8620
  const brand = this.sanitizeColor(campaign.background_color || "#4169e1");
7352
- const branded = ic.surface_style === "branded";
7353
- if (branded) {
8621
+ const surface = ic.surface_style;
8622
+ if (surface === "branded") {
7354
8623
  const bg = brand;
7355
8624
  const text = campaign.text_color ? this.sanitizeColor(campaign.text_color) : "#ffffff";
7356
8625
  return { bg, text, btnBg: text, btnText: bg, accent: text, branded: true };
7357
8626
  }
8627
+ if (surface === "dark") {
8628
+ const bg = this.sanitizeColor(campaign.background_color || "#0f172a");
8629
+ const text = campaign.text_color ? this.sanitizeColor(campaign.text_color) : "#ffffff";
8630
+ return { bg, text, btnBg: "#ffffff", btnText: bg, accent: text, branded: false };
8631
+ }
7358
8632
  return { bg: "#ffffff", text: "#0f172a", btnBg: brand, btnText: this._contrastText(brand), accent: brand, branded: false };
7359
8633
  }
7360
8634
  inAppStyle(campaign) {
@@ -7458,7 +8732,7 @@ const _AegisInAppManager = class _AegisInAppManager {
7458
8732
  actionsContainer.appendChild(ctaButton);
7459
8733
  }
7460
8734
  const closeButton = document.createElement("button");
7461
- closeButton.textContent = "";
8735
+ closeButton.innerHTML = lucideSvg("x", { size: 18 });
7462
8736
  closeButton.setAttribute("aria-label", "Close");
7463
8737
  closeButton.style.cssText = `
7464
8738
  background: transparent;
@@ -7618,7 +8892,7 @@ const _AegisInAppManager = class _AegisInAppManager {
7618
8892
  actions.appendChild(closeButton);
7619
8893
  content.appendChild(actions);
7620
8894
  modal.appendChild(content);
7621
- overlay.appendChild(modal);
8895
+ overlay.appendChild(this._wrapScaled(modal));
7622
8896
  if (ic.modal_dismiss_on_overlay !== false) {
7623
8897
  overlay.addEventListener("click", (e) => {
7624
8898
  if (e.target === overlay) {
@@ -7656,7 +8930,7 @@ const _AegisInAppManager = class _AegisInAppManager {
7656
8930
  pill.replaceChildren();
7657
8931
  const ic = document.createElement("span");
7658
8932
  ic.style.fontSize = "13px";
7659
- ic.textContent = v.muted ? "🔇" : "🔊";
8933
+ ic.innerHTML = lucideSvg(v.muted ? "volume-x" : "volume-2", { size: 13 });
7660
8934
  pill.appendChild(ic);
7661
8935
  if (v.muted) {
7662
8936
  const t = document.createElement("span");
@@ -7786,7 +9060,7 @@ const _AegisInAppManager = class _AegisInAppManager {
7786
9060
  }
7787
9061
  overlay.appendChild(contentContainer);
7788
9062
  const closeButton = document.createElement("button");
7789
- closeButton.textContent = "";
9063
+ closeButton.innerHTML = lucideSvg("x", { size: isBg ? 18 : 28 });
7790
9064
  closeButton.setAttribute("aria-label", "Close");
7791
9065
  closeButton.style.cssText = `
7792
9066
  position: absolute; top: 20px; right: 20px; z-index: 3; background: ${isBg ? "rgba(0,0,0,0.45)" : "transparent"};
@@ -7914,7 +9188,7 @@ const _AegisInAppManager = class _AegisInAppManager {
7914
9188
  }
7915
9189
  modal.appendChild(content);
7916
9190
  const closeButton = document.createElement("button");
7917
- closeButton.textContent = "";
9191
+ closeButton.innerHTML = lucideSvg("x", { size: 22 });
7918
9192
  closeButton.setAttribute("aria-label", "Close");
7919
9193
  closeButton.style.cssText = `
7920
9194
  position: absolute;
@@ -7987,16 +9261,16 @@ const _AegisInAppManager = class _AegisInAppManager {
7987
9261
  const ic = campaign.interactive_config || {};
7988
9262
  const variant = ic.alert_variant || "";
7989
9263
  const VARIANTS = {
7990
- info: { glyph: "", color: "#3b82f6" },
7991
- success: { glyph: "", color: "#10b981" },
7992
- warning: { glyph: "!", color: "#f59e0b" },
7993
- error: { glyph: "", color: "#ef4444" }
9264
+ info: { icon: "info", color: "#3b82f6" },
9265
+ success: { icon: "check", color: "#10b981" },
9266
+ warning: { icon: "triangle-alert", color: "#f59e0b" },
9267
+ error: { icon: "x", color: "#ef4444" }
7994
9268
  };
7995
9269
  const vspec = VARIANTS[variant];
7996
9270
  if (vspec) {
7997
9271
  const badge = document.createElement("div");
7998
9272
  badge.style.cssText = `width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: 800; background: ${vspec.color}22; color: ${vspec.color};`;
7999
- badge.textContent = vspec.glyph;
9273
+ badge.innerHTML = lucideSvg(vspec.icon, { size: 24 });
8000
9274
  content.appendChild(badge);
8001
9275
  }
8002
9276
  const title = document.createElement("h3");
@@ -8138,7 +9412,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8138
9412
  if (!hasMedia) {
8139
9413
  const placeholder = document.createElement("div");
8140
9414
  placeholder.style.cssText = "width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #1f2937, #0f172a); color: rgba(255,255,255,0.35); font-size: 34px;";
8141
- placeholder.textContent = "";
9415
+ placeholder.innerHTML = lucideSvg("play", { size: 34 });
8142
9416
  mediaWrap.appendChild(placeholder);
8143
9417
  }
8144
9418
  pip.appendChild(mediaWrap);
@@ -8149,7 +9423,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8149
9423
  playGlyph.setAttribute("aria-label", "Play or pause");
8150
9424
  playGlyph.style.cssText = "position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 52px; height: 52px; border-radius: 50%; border: none; background: rgba(0,0,0,0.45); color: #fff; font-size: 22px; cursor: pointer; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.25s ease; backdrop-filter: blur(2px);";
8151
9425
  const syncGlyph = () => {
8152
- playGlyph.textContent = v.paused ? "" : "❚❚";
9426
+ playGlyph.innerHTML = lucideSvg(v.paused ? "play" : "pause", { size: 22 });
8153
9427
  playGlyph.style.opacity = v.paused ? "1" : "0";
8154
9428
  };
8155
9429
  const togglePlay = () => {
@@ -8176,7 +9450,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8176
9450
  mutePill.replaceChildren();
8177
9451
  const ic2 = document.createElement("span");
8178
9452
  ic2.style.fontSize = "13px";
8179
- ic2.textContent = v.muted ? "🔇" : "🔊";
9453
+ ic2.innerHTML = lucideSvg(v.muted ? "volume-x" : "volume-2", { size: 13 });
8180
9454
  mutePill.appendChild(ic2);
8181
9455
  if (v.muted) {
8182
9456
  const t = document.createElement("span");
@@ -8271,7 +9545,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8271
9545
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
8272
9546
  `;
8273
9547
  if (expandBtn) {
8274
- expandBtn.textContent = "";
9548
+ expandBtn.innerHTML = lucideSvg("x", { size: 16 });
8275
9549
  expandBtn.setAttribute("aria-label", "Minimize");
8276
9550
  }
8277
9551
  if (video && video.muted) {
@@ -8294,7 +9568,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8294
9568
  }
8295
9569
  const closeButton = document.createElement("button");
8296
9570
  closeButton.type = "button";
8297
- closeButton.textContent = "";
9571
+ closeButton.innerHTML = lucideSvg("x", { size: 16 });
8298
9572
  closeButton.setAttribute("aria-label", "Close");
8299
9573
  closeButton.style.cssText = ctrlBtnCss;
8300
9574
  closeButton.addEventListener("click", (e) => {
@@ -9054,7 +10328,7 @@ _AegisInAppManager.KNOWN_SURFACES = /* @__PURE__ */ new Set([
9054
10328
  ]);
9055
10329
  _AegisInAppManager.SERVED_DEDUP_PREFIX = "aegis_served_fired:";
9056
10330
  let AegisInAppManager = _AegisInAppManager;
9057
- function renderPreview(config) {
10331
+ function renderPreview(config, opts) {
9058
10332
  document.querySelectorAll(
9059
10333
  '[class^="aegis-in-app-"]'
9060
10334
  ).forEach((el) => {
@@ -9078,12 +10352,33 @@ function renderPreview(config) {
9078
10352
  writeKey: "preview-mode",
9079
10353
  apiHost: "",
9080
10354
  debugMode: false,
9081
- enableSSE: false
10355
+ enableSSE: false,
10356
+ ...(opts == null ? void 0 : opts.cart) ? { getCartState: () => opts.cart } : {}
9082
10357
  });
9083
10358
  const m2 = manager;
9084
10359
  m2.trackEvent = async () => {
9085
10360
  };
9086
10361
  m2.addAnimationStyles();
10362
+ const cfgRec = config;
10363
+ const deliveryModes = Array.isArray(cfgRec.delivery_modes) ? cfgRec.delivery_modes : [];
10364
+ const widgetCategory = typeof cfgRec.widget_category === "string" ? cfgRec.widget_category : "";
10365
+ const inline = deliveryModes.includes("embedded_card") && !!widgetCategory;
10366
+ const coGroup = (opts == null ? void 0 : opts.coGroup) ?? [];
10367
+ if (typeof document !== "undefined" && (coGroup.length > 0 || inline)) {
10368
+ const anchors = document.querySelectorAll("[data-aegis-slot]");
10369
+ if (anchors.length > 0) {
10370
+ try {
10371
+ anchors.forEach((slot) => {
10372
+ slot.querySelectorAll(":scope > :not([data-aegis-slot-default])").forEach((el) => el.remove());
10373
+ });
10374
+ const mm = manager;
10375
+ mm.campaigns = (inline ? [config, ...coGroup] : coGroup).map((c) => ({ ...c, surface: void 0, target_screens: void 0 })).sort((a, b) => (b.priority || 0) - (a.priority || 0));
10376
+ mm.renderIntoSlots();
10377
+ if (inline) return;
10378
+ } catch {
10379
+ }
10380
+ }
10381
+ }
9087
10382
  if (config.type === "stories" || config.sub_type === "stories") {
9088
10383
  const host = document.createElement("div");
9089
10384
  host.className = "aegis-in-app-stories-host";
@@ -9293,13 +10588,29 @@ function evaluateRule(rule, snapshot, namespaces) {
9293
10588
  const result = evaluateExpr(rule.when, snapshot, namespaces);
9294
10589
  return result === true;
9295
10590
  }
9296
- class IntentRuleEvaluator {
10591
+ const _IntentRuleEvaluator = class _IntentRuleEvaluator {
9297
10592
  constructor() {
9298
10593
  this.armed = [];
9299
10594
  this.snapshot = {};
9300
10595
  this.namespaces = {};
9301
10596
  this.firedThisSession = /* @__PURE__ */ new Set();
9302
10597
  this.silencedThisSession = /* @__PURE__ */ new Set();
10598
+ this.oneInterruptionPerSession = false;
10599
+ this.interruptiveBudgetSpent = false;
10600
+ this.ttfiMs = 0;
10601
+ this.sessionStartMs = Date.now();
10602
+ this.nowFn = () => Date.now();
10603
+ }
10604
+ /** Tune governance. `now` also (re)bases the TTFI session clock. */
10605
+ setGovernance(opts) {
10606
+ if (opts.oneInterruptionPerSession !== void 0) {
10607
+ this.oneInterruptionPerSession = opts.oneInterruptionPerSession;
10608
+ }
10609
+ if (opts.ttfiMs !== void 0) this.ttfiMs = opts.ttfiMs;
10610
+ if (opts.now) {
10611
+ this.nowFn = opts.now;
10612
+ this.sessionStartMs = opts.now();
10613
+ }
9303
10614
  }
9304
10615
  // ─── snapshot management ───
9305
10616
  updateSignal(signal, value) {
@@ -9335,12 +10646,15 @@ class IntentRuleEvaluator {
9335
10646
  }
9336
10647
  markFired(campaignId) {
9337
10648
  this.firedThisSession.add(campaignId);
10649
+ this.interruptiveBudgetSpent = true;
9338
10650
  }
9339
10651
  /** Clear all per-session state. Caller invokes on logout or explicit
9340
10652
  * session reset. */
9341
10653
  reset() {
9342
10654
  this.firedThisSession.clear();
9343
10655
  this.silencedThisSession.clear();
10656
+ this.interruptiveBudgetSpent = false;
10657
+ this.sessionStartMs = this.nowFn();
9344
10658
  }
9345
10659
  // ─── the main dispatch ───
9346
10660
  /**
@@ -9363,6 +10677,11 @@ class IntentRuleEvaluator {
9363
10677
  * 5. If nothing matches, return 'none'.
9364
10678
  */
9365
10679
  onSignalChanged(signal) {
10680
+ if (this.oneInterruptionPerSession && this.interruptiveBudgetSpent) {
10681
+ return { kind: "none" };
10682
+ }
10683
+ const sessionElapsedMs = this.nowFn() - this.sessionStartMs;
10684
+ const exemptFromTtfi = _IntentRuleEvaluator.EXIT_SIGNALS.includes(signal);
9366
10685
  const candidates = this.armed.filter((c) => c.rule.fire_on.includes(signal)).filter((c) => !this.firedThisSession.has(c.id)).filter((c) => !this.silencedThisSession.has(c.id)).sort((a, b) => {
9367
10686
  if (b.rule.priority !== a.rule.priority) {
9368
10687
  return b.rule.priority - a.rule.priority;
@@ -9372,6 +10691,9 @@ class IntentRuleEvaluator {
9372
10691
  for (let i = 0; i < candidates.length; i++) {
9373
10692
  const c = candidates[i];
9374
10693
  if (!evaluateRule(c.rule, this.snapshot, this.namespaces)) continue;
10694
+ if (this.ttfiMs > 0 && sessionElapsedMs < this.ttfiMs && !exemptFromTtfi) {
10695
+ continue;
10696
+ }
9375
10697
  if (c.rule.suppress_competing) {
9376
10698
  const suppressIds = this.armed.filter((other) => other.id !== c.id).map((other) => other.id);
9377
10699
  suppressIds.forEach((id) => this.silencedThisSession.add(id));
@@ -9385,7 +10707,12 @@ class IntentRuleEvaluator {
9385
10707
  }
9386
10708
  return { kind: "none" };
9387
10709
  }
9388
- }
10710
+ };
10711
+ _IntentRuleEvaluator.EXIT_SIGNALS = [
10712
+ "exit_intent",
10713
+ "back_button"
10714
+ ];
10715
+ let IntentRuleEvaluator = _IntentRuleEvaluator;
9389
10716
  class ContactScoresFetcher {
9390
10717
  constructor(cfg) {
9391
10718
  this.lastFetchedAt = 0;
@@ -9739,7 +11066,7 @@ class PrefetchBundleClient {
9739
11066
  }
9740
11067
  let response;
9741
11068
  try {
9742
- response = await fetch(url, { method: "GET", headers });
11069
+ response = await fetch(url, { method: "GET", headers, cache: "no-cache" });
9743
11070
  } catch (err) {
9744
11071
  logger.warn("prefetch-bundle fetch network error:", err);
9745
11072
  return this.currentBundle;
@@ -10764,8 +12091,8 @@ class AegisWidgetManager {
10764
12091
  { label: "Prize 3", color: "#48dbfb" },
10765
12092
  { label: "Prize 4", color: "#ff6348" }
10766
12093
  ];
10767
- const SVG_NS = "http://www.w3.org/2000/svg";
10768
- const wheel = document.createElementNS(SVG_NS, "svg");
12094
+ const SVG_NS2 = "http://www.w3.org/2000/svg";
12095
+ const wheel = document.createElementNS(SVG_NS2, "svg");
10769
12096
  wheel.setAttribute("viewBox", "-160 -160 320 320");
10770
12097
  wheel.setAttribute("width", "300");
10771
12098
  wheel.setAttribute("height", "300");
@@ -10781,7 +12108,7 @@ class AegisWidgetManager {
10781
12108
  const x2 = Math.cos(end) * radius;
10782
12109
  const y2 = Math.sin(end) * radius;
10783
12110
  const largeArc = anglePer > Math.PI ? 1 : 0;
10784
- const path = document.createElementNS(SVG_NS, "path");
12111
+ const path = document.createElementNS(SVG_NS2, "path");
10785
12112
  path.setAttribute(
10786
12113
  "d",
10787
12114
  `M 0 0 L ${x1.toFixed(2)} ${y1.toFixed(2)} A ${radius} ${radius} 0 ${largeArc} 1 ${x2.toFixed(2)} ${y2.toFixed(2)} Z`
@@ -10793,7 +12120,7 @@ class AegisWidgetManager {
10793
12120
  const labelAngle = start + anglePer / 2;
10794
12121
  const lx = Math.cos(labelAngle) * radius * 0.65;
10795
12122
  const ly = Math.sin(labelAngle) * radius * 0.65;
10796
- const text = document.createElementNS(SVG_NS, "text");
12123
+ const text = document.createElementNS(SVG_NS2, "text");
10797
12124
  text.setAttribute("x", lx.toFixed(2));
10798
12125
  text.setAttribute("y", ly.toFixed(2));
10799
12126
  text.setAttribute("fill", "#ffffff");
@@ -10905,7 +12232,8 @@ class AegisWidgetManager {
10905
12232
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
10906
12233
  `;
10907
12234
  const closeBtn = document.createElement("button");
10908
- closeBtn.innerHTML = "";
12235
+ closeBtn.innerHTML = lucideSvg("x", { size: 16 });
12236
+ closeBtn.setAttribute("aria-label", "Close");
10909
12237
  closeBtn.className = "aegis-spin-wheel-close";
10910
12238
  closeBtn.style.cssText = `
10911
12239
  position: absolute;
@@ -11885,8 +13213,9 @@ class AegisWidgetManager {
11885
13213
  }
11886
13214
  if (config.show_timer && config.timer_minutes) {
11887
13215
  const timer = document.createElement("div");
11888
- timer.style.cssText = "margin: 0 0 24px 0; text-align: center; font-size: 14px; color: #999;";
11889
- timer.textContent = `⏰ Offer expires in ${config.timer_minutes} minutes`;
13216
+ timer.style.cssText = "margin: 0 0 24px 0; display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 14px; color: #999;";
13217
+ timer.innerHTML = lucideSvg("clock", { size: 14 });
13218
+ timer.appendChild(document.createTextNode(`Offer expires in ${config.timer_minutes} minutes`));
11890
13219
  modal.appendChild(timer);
11891
13220
  }
11892
13221
  const ctaButton = document.createElement("button");
@@ -12017,7 +13346,7 @@ class AegisWidgetManager {
12017
13346
  });
12018
13347
  modal.innerHTML = `
12019
13348
  <div style="text-align: center; padding: 20px;">
12020
- <div style="font-size: 48px; margin-bottom: 16px;">✅</div>
13349
+ <div style="margin-bottom: 16px; color: #10b981;">${lucideSvg("check", { size: 48 })}</div>
12021
13350
  <h3 style="margin: 0 0 12px 0; color: #1a73e8;">Thank You!</h3>
12022
13351
  <p style="margin: 0; color: #666;">Check your email for your discount code!</p>
12023
13352
  </div>
@@ -12263,6 +13592,9 @@ class AegisMessageRuntime {
12263
13592
  getCartState: () => this.deriveCartState()
12264
13593
  });
12265
13594
  this.intentRuleEvaluator = new IntentRuleEvaluator();
13595
+ if (config.governance) {
13596
+ this.intentRuleEvaluator.setGovernance(config.governance);
13597
+ }
12266
13598
  this.contactScores = new ContactScoresFetcher({
12267
13599
  // Falls back to same-origin when no apiHost configured (storefront
12268
13600
  // typical case) — matches WidgetManager/InAppManager handling
@@ -12592,10 +13924,13 @@ export {
12592
13924
  TriggerEngine,
12593
13925
  U as UserNamespace,
12594
13926
  bootstrap,
13927
+ closeChat,
12595
13928
  debounce,
12596
13929
  aegis as default,
12597
13930
  deriveDeviceFingerprint,
13931
+ getCurrentLauncher,
12598
13932
  m as murmurhash3_x86_32,
13933
+ openChat,
12599
13934
  readFirstPartyCookie,
12600
13935
  renderPreview,
12601
13936
  throttle,