@active-reach/web-sdk 1.19.0 → 1.21.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.
- package/dist/aegis.min.js +1 -1
- package/dist/aegis.min.js.map +1 -1
- package/dist/cdn.d.ts.map +1 -1
- package/dist/chat/AegisChat.d.ts +24 -0
- package/dist/chat/AegisChat.d.ts.map +1 -1
- package/dist/chat/index.d.ts +1 -1
- package/dist/chat/index.d.ts.map +1 -1
- package/dist/inapp/AegisInAppManager.d.ts +17 -1
- package/dist/inapp/AegisInAppManager.d.ts.map +1 -1
- package/dist/inapp/renderPreview.d.ts +16 -1
- package/dist/inapp/renderPreview.d.ts.map +1 -1
- package/dist/inapp/renderers/active-web-chat.d.ts +7 -0
- package/dist/inapp/renderers/active-web-chat.d.ts.map +1 -1
- package/dist/inapp/renderers/hero.d.ts +23 -0
- package/dist/inapp/renderers/hero.d.ts.map +1 -0
- package/dist/inapp/renderers/index.d.ts +1 -0
- package/dist/inapp/renderers/index.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +766 -160
- package/dist/index.js.map +1 -1
- package/dist/runtime/AegisMessageRuntime.d.ts +37 -20
- package/dist/runtime/AegisMessageRuntime.d.ts.map +1 -1
- package/dist/triggers/ContactScoresFetcher.d.ts +6 -0
- package/dist/triggers/ContactScoresFetcher.d.ts.map +1 -1
- package/dist/triggers/IntentRuleEvaluator.d.ts +29 -4
- package/dist/triggers/IntentRuleEvaluator.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -2095,7 +2095,7 @@ function renderGame(ctx, subType) {
|
|
|
2095
2095
|
fn(ctx);
|
|
2096
2096
|
return true;
|
|
2097
2097
|
}
|
|
2098
|
-
const ASPECT = { "16:9": "16 / 9", "9:16": "9 / 16", "1:1": "1 / 1", "4:5": "4 / 5" };
|
|
2098
|
+
const ASPECT$1 = { "16:9": "16 / 9", "9:16": "9 / 16", "1:1": "1 / 1", "4:5": "4 / 5" };
|
|
2099
2099
|
function buildVideo(ctx, aspect, autoplay, loop) {
|
|
2100
2100
|
var _a;
|
|
2101
2101
|
const { campaign, sanitizeUrl } = ctx;
|
|
@@ -2103,7 +2103,7 @@ function buildVideo(ctx, aspect, autoplay, loop) {
|
|
|
2103
2103
|
const videoUrl = sanitizeUrl(ic.video_url || campaign.video_url || "");
|
|
2104
2104
|
const poster = campaign.image_url ? sanitizeUrl(campaign.image_url) : null;
|
|
2105
2105
|
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;`;
|
|
2106
|
+
wrap.style.cssText = `position:relative;width:100%;aspect-ratio:${ASPECT$1[aspect] || ASPECT$1["16:9"]};background:#0b1220;border-radius:14px;overflow:hidden;`;
|
|
2107
2107
|
if (videoUrl) {
|
|
2108
2108
|
const vid = document.createElement("video");
|
|
2109
2109
|
vid.src = videoUrl;
|
|
@@ -2272,11 +2272,179 @@ function renderVideoInline(ctx, target) {
|
|
|
2272
2272
|
target.appendChild(banner);
|
|
2273
2273
|
return true;
|
|
2274
2274
|
}
|
|
2275
|
+
const HEX = /^#[0-9a-fA-F]{3,8}$/;
|
|
2276
|
+
function safeColor(c, fallback) {
|
|
2277
|
+
return c && HEX.test(c) ? c : fallback;
|
|
2278
|
+
}
|
|
2279
|
+
const RADIUS = { none: "0", lg: "12px", xl: "16px", "2xl": "20px" };
|
|
2280
|
+
const ASPECT = { "16:9": "16 / 9", "4:3": "4 / 3", "21:9": "21 / 9" };
|
|
2281
|
+
function cardHeadline(c) {
|
|
2282
|
+
return c.headline ?? c.title ?? "";
|
|
2283
|
+
}
|
|
2284
|
+
function cardSub(c) {
|
|
2285
|
+
return c.subhead ?? c.body ?? "";
|
|
2286
|
+
}
|
|
2287
|
+
function cardCta(c) {
|
|
2288
|
+
return c.cta_label ?? c.cta_text ?? "";
|
|
2289
|
+
}
|
|
2290
|
+
function cardUrl(c) {
|
|
2291
|
+
if (c.cta_url) return c.cta_url;
|
|
2292
|
+
const t = c.cta_target;
|
|
2293
|
+
if (t && t.value) return t.type === "category" ? `?category=${encodeURIComponent(t.value)}` : t.value;
|
|
2294
|
+
return "";
|
|
2295
|
+
}
|
|
2296
|
+
function renderHeroInline(ctx, target) {
|
|
2297
|
+
const { campaign, sanitizeUrl, trackEvent } = ctx;
|
|
2298
|
+
const ic = campaign.interactive_config || {};
|
|
2299
|
+
const cards = Array.isArray(ic.cards) ? ic.cards : [];
|
|
2300
|
+
if (cards.length === 0) return false;
|
|
2301
|
+
const chrome = ic.chrome || {};
|
|
2302
|
+
const accent = safeColor(
|
|
2303
|
+
chrome.accent && chrome.accent !== "brand" ? chrome.accent : campaign.background_color,
|
|
2304
|
+
"#4169e1"
|
|
2305
|
+
);
|
|
2306
|
+
const radius = RADIUS[chrome.radius ?? "2xl"] ?? RADIUS["2xl"];
|
|
2307
|
+
const variant = chrome.variant ?? "hero_fullbleed";
|
|
2308
|
+
if (variant === "announcement_bar") {
|
|
2309
|
+
const c = cards[0];
|
|
2310
|
+
const bar = document.createElement("div");
|
|
2311
|
+
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;`;
|
|
2312
|
+
bar.textContent = [cardHeadline(c), cardSub(c)].filter(Boolean).join(" · ");
|
|
2313
|
+
const url = sanitizeUrl(cardUrl(c));
|
|
2314
|
+
if (url && cardCta(c)) {
|
|
2315
|
+
const a = document.createElement("span");
|
|
2316
|
+
a.style.cssText = "text-decoration:underline;cursor:pointer;white-space:nowrap;";
|
|
2317
|
+
a.textContent = cardCta(c);
|
|
2318
|
+
a.addEventListener("click", () => {
|
|
2319
|
+
var _a;
|
|
2320
|
+
trackEvent(campaign.id, "clicked");
|
|
2321
|
+
(_a = ctx.navigate) == null ? void 0 : _a.call(ctx, url);
|
|
2322
|
+
});
|
|
2323
|
+
bar.appendChild(a);
|
|
2324
|
+
}
|
|
2325
|
+
target.appendChild(bar);
|
|
2326
|
+
trackEvent(campaign.id, "impression");
|
|
2327
|
+
return true;
|
|
2328
|
+
}
|
|
2329
|
+
const root = document.createElement("div");
|
|
2330
|
+
root.style.cssText = `position:relative;width:100%;overflow:hidden;border-radius:${radius};box-shadow:0 1px 2px rgba(16,24,40,0.06);${chrome.aspect_ratio && ASPECT[chrome.aspect_ratio] ? `aspect-ratio:${ASPECT[chrome.aspect_ratio]};` : "min-height:300px;"}`;
|
|
2331
|
+
root.setAttribute("data-campaign-id", campaign.id);
|
|
2332
|
+
const track = document.createElement("div");
|
|
2333
|
+
track.style.cssText = "display:flex;height:100%;width:100%;transition:transform 0.5s ease;";
|
|
2334
|
+
root.appendChild(track);
|
|
2335
|
+
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);";
|
|
2336
|
+
const justify = chrome.text_position === "center" ? "center" : "flex-end";
|
|
2337
|
+
const align = chrome.text_position === "bottom-left" || !chrome.text_position ? "flex-start" : "center";
|
|
2338
|
+
cards.forEach((c, i) => {
|
|
2339
|
+
const slide = document.createElement("div");
|
|
2340
|
+
slide.style.cssText = "position:relative;flex:0 0 100%;width:100%;height:100%;min-height:inherit;background:#f1f5f9;";
|
|
2341
|
+
const videoUrl = sanitizeUrl(c.video_url ?? "");
|
|
2342
|
+
const isVideo = !!videoUrl || c.media_type === "video";
|
|
2343
|
+
const mediaUrl = isVideo ? videoUrl || sanitizeUrl(c.media_url ?? "") : sanitizeUrl(c.image_url ?? c.media_url ?? "");
|
|
2344
|
+
if (mediaUrl) {
|
|
2345
|
+
if (isVideo) {
|
|
2346
|
+
const v = document.createElement("video");
|
|
2347
|
+
v.src = mediaUrl;
|
|
2348
|
+
v.autoplay = true;
|
|
2349
|
+
v.muted = true;
|
|
2350
|
+
v.loop = true;
|
|
2351
|
+
v.playsInline = true;
|
|
2352
|
+
v.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-fit:cover;";
|
|
2353
|
+
slide.appendChild(v);
|
|
2354
|
+
} else {
|
|
2355
|
+
const img = document.createElement("div");
|
|
2356
|
+
img.style.cssText = `position:absolute;inset:0;background:url("${mediaUrl}") center/cover no-repeat;`;
|
|
2357
|
+
slide.appendChild(img);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
if (overlayCss) {
|
|
2361
|
+
const scrim = document.createElement("div");
|
|
2362
|
+
scrim.style.cssText = `position:absolute;inset:0;${overlayCss}`;
|
|
2363
|
+
slide.appendChild(scrim);
|
|
2364
|
+
}
|
|
2365
|
+
const content = document.createElement("div");
|
|
2366
|
+
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"};`;
|
|
2367
|
+
const h = cardHeadline(c);
|
|
2368
|
+
if (h) {
|
|
2369
|
+
const head = document.createElement("h2");
|
|
2370
|
+
head.textContent = h;
|
|
2371
|
+
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%;";
|
|
2372
|
+
content.appendChild(head);
|
|
2373
|
+
}
|
|
2374
|
+
const s = cardSub(c);
|
|
2375
|
+
if (s) {
|
|
2376
|
+
const sub = document.createElement("p");
|
|
2377
|
+
sub.textContent = s;
|
|
2378
|
+
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%;";
|
|
2379
|
+
content.appendChild(sub);
|
|
2380
|
+
}
|
|
2381
|
+
const cta = cardCta(c);
|
|
2382
|
+
const url = sanitizeUrl(cardUrl(c));
|
|
2383
|
+
if (cta && chrome.cta_style !== "none") {
|
|
2384
|
+
const btn = document.createElement("button");
|
|
2385
|
+
btn.textContent = cta;
|
|
2386
|
+
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);`;
|
|
2387
|
+
btn.addEventListener("click", () => {
|
|
2388
|
+
var _a;
|
|
2389
|
+
trackEvent(campaign.id, "clicked", { stepId: `card_${i}` });
|
|
2390
|
+
if (url) (_a = ctx.navigate) == null ? void 0 : _a.call(ctx, url);
|
|
2391
|
+
});
|
|
2392
|
+
content.appendChild(btn);
|
|
2393
|
+
}
|
|
2394
|
+
slide.appendChild(content);
|
|
2395
|
+
track.appendChild(slide);
|
|
2396
|
+
});
|
|
2397
|
+
const dotEls = [];
|
|
2398
|
+
let activeIdx = 0;
|
|
2399
|
+
const goto = (idx) => {
|
|
2400
|
+
activeIdx = (idx % cards.length + cards.length) % cards.length;
|
|
2401
|
+
track.style.transform = `translateX(-${activeIdx * 100}%)`;
|
|
2402
|
+
dotEls.forEach((d, i) => {
|
|
2403
|
+
d.style.width = i === activeIdx ? "18px" : "6px";
|
|
2404
|
+
d.style.background = i === activeIdx ? accent : "rgba(255,255,255,0.7)";
|
|
2405
|
+
});
|
|
2406
|
+
};
|
|
2407
|
+
const dots = document.createElement("div");
|
|
2408
|
+
dots.style.cssText = "position:absolute;bottom:8px;left:50%;transform:translateX(-50%);display:flex;gap:6px;z-index:2;";
|
|
2409
|
+
if (cards.length > 1) {
|
|
2410
|
+
cards.forEach((_, i) => {
|
|
2411
|
+
const dot = document.createElement("span");
|
|
2412
|
+
dot.style.cssText = "width:6px;height:6px;border-radius:999px;background:rgba(255,255,255,0.7);transition:all 0.25s;cursor:pointer;";
|
|
2413
|
+
dot.addEventListener("click", () => goto(i));
|
|
2414
|
+
dotEls.push(dot);
|
|
2415
|
+
dots.appendChild(dot);
|
|
2416
|
+
});
|
|
2417
|
+
root.appendChild(dots);
|
|
2418
|
+
}
|
|
2419
|
+
target.appendChild(root);
|
|
2420
|
+
goto(0);
|
|
2421
|
+
if (cards.length > 1 && chrome.loop !== false) {
|
|
2422
|
+
const ms = typeof chrome.autoplay_ms === "number" && chrome.autoplay_ms >= 1500 ? chrome.autoplay_ms : 4e3;
|
|
2423
|
+
let timer = window.setInterval(() => goto(activeIdx + 1), ms);
|
|
2424
|
+
root.addEventListener("mouseenter", () => window.clearInterval(timer));
|
|
2425
|
+
root.addEventListener("mouseleave", () => {
|
|
2426
|
+
timer = window.setInterval(() => goto(activeIdx + 1), ms);
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
trackEvent(campaign.id, "impression");
|
|
2430
|
+
return true;
|
|
2431
|
+
}
|
|
2275
2432
|
const STYLE_ID = "aegis-chat-styles";
|
|
2276
2433
|
const POLL_INTERVAL_MS = 5e3;
|
|
2434
|
+
let currentLauncher = null;
|
|
2435
|
+
function getCurrentLauncher() {
|
|
2436
|
+
return currentLauncher;
|
|
2437
|
+
}
|
|
2438
|
+
function openChat(prefill) {
|
|
2439
|
+
currentLauncher == null ? void 0 : currentLauncher.openPanel(prefill);
|
|
2440
|
+
}
|
|
2441
|
+
function closeChat() {
|
|
2442
|
+
currentLauncher == null ? void 0 : currentLauncher.closePanel();
|
|
2443
|
+
}
|
|
2277
2444
|
const ATTENTION_DELAY_MS = 4500;
|
|
2278
2445
|
class AegisChat {
|
|
2279
2446
|
constructor(config) {
|
|
2447
|
+
this.sessionResumed = false;
|
|
2280
2448
|
this.open = false;
|
|
2281
2449
|
this.initialized = false;
|
|
2282
2450
|
this.unread = 0;
|
|
@@ -2294,12 +2462,14 @@ class AegisChat {
|
|
|
2294
2462
|
this.icon = config.icon ?? "sparkle";
|
|
2295
2463
|
this.logoUrl = config.logoUrl;
|
|
2296
2464
|
this.position = config.position ?? "bottom-right";
|
|
2465
|
+
this.displayMode = config.displayMode ?? "bubble";
|
|
2297
2466
|
this.agentPersona = config.agentPersona;
|
|
2298
2467
|
this.quickReplies = config.quickReplies;
|
|
2299
2468
|
this.onSessionStart = config.onSessionStart;
|
|
2300
2469
|
this.onSessionEnd = config.onSessionEnd;
|
|
2301
2470
|
this.onMessageSent = config.onMessageSent;
|
|
2302
2471
|
this.anonymousId = this.readAnonId();
|
|
2472
|
+
this.contactId = this.contactId ?? this.readStoredContactId();
|
|
2303
2473
|
}
|
|
2304
2474
|
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
2305
2475
|
initialize() {
|
|
@@ -2307,7 +2477,8 @@ class AegisChat {
|
|
|
2307
2477
|
this.initialized = true;
|
|
2308
2478
|
this.injectStyles();
|
|
2309
2479
|
this.mount();
|
|
2310
|
-
this
|
|
2480
|
+
currentLauncher = this;
|
|
2481
|
+
if (this.displayMode !== "nav") this.scheduleAttention();
|
|
2311
2482
|
}
|
|
2312
2483
|
/** Called by the runtime when the visitor identifies. */
|
|
2313
2484
|
updateContactId(contactId) {
|
|
@@ -2330,7 +2501,7 @@ class AegisChat {
|
|
|
2330
2501
|
(_c = this.bubble) == null ? void 0 : _c.classList.add("aegis-chat-bubble--hidden");
|
|
2331
2502
|
this.clearUnread();
|
|
2332
2503
|
if (prefill && this.input) this.input.value = prefill;
|
|
2333
|
-
void this.
|
|
2504
|
+
void this.resumeSession();
|
|
2334
2505
|
this.startPolling();
|
|
2335
2506
|
(_d = this.input) == null ? void 0 : _d.focus();
|
|
2336
2507
|
}
|
|
@@ -2355,13 +2526,22 @@ class AegisChat {
|
|
|
2355
2526
|
this.stopPolling();
|
|
2356
2527
|
(_a = this.root) == null ? void 0 : _a.remove();
|
|
2357
2528
|
this.initialized = false;
|
|
2529
|
+
if (currentLauncher === this) currentLauncher = null;
|
|
2358
2530
|
}
|
|
2359
2531
|
// ── Send ─────────────────────────────────────────────────────────────────
|
|
2360
|
-
async send(text) {
|
|
2532
|
+
async send(text, media) {
|
|
2361
2533
|
var _a;
|
|
2362
|
-
const
|
|
2363
|
-
if (!
|
|
2364
|
-
|
|
2534
|
+
const caption2 = text.trim();
|
|
2535
|
+
if (!caption2 && !media) return;
|
|
2536
|
+
const message = caption2 || (media ? media.type === "image" ? "Sent a photo 📷" : "Sent a file 📎" : "");
|
|
2537
|
+
this.appendBubble({
|
|
2538
|
+
id: `local_${Date.now()}`,
|
|
2539
|
+
sender: "customer",
|
|
2540
|
+
role: "user",
|
|
2541
|
+
content: message,
|
|
2542
|
+
media_url: (media == null ? void 0 : media.url) ?? null,
|
|
2543
|
+
media_type: (media == null ? void 0 : media.type) ?? null
|
|
2544
|
+
});
|
|
2365
2545
|
this.pendingEcho.push(message);
|
|
2366
2546
|
if (this.input) {
|
|
2367
2547
|
this.input.value = "";
|
|
@@ -2377,6 +2557,8 @@ class AegisChat {
|
|
|
2377
2557
|
channel: this.channel,
|
|
2378
2558
|
anonymous_id: this.anonymousId,
|
|
2379
2559
|
contact_id: this.contactId,
|
|
2560
|
+
media_url: media == null ? void 0 : media.url,
|
|
2561
|
+
media_type: media == null ? void 0 : media.type,
|
|
2380
2562
|
// Active Web Chat — binds the campaign-configured persona. Optional;
|
|
2381
2563
|
// the backend falls back to the tenant default if unknown/absent
|
|
2382
2564
|
// (honored server-side in Phase E). JSON.stringify drops it if absent.
|
|
@@ -2395,6 +2577,7 @@ class AegisChat {
|
|
|
2395
2577
|
} catch {
|
|
2396
2578
|
}
|
|
2397
2579
|
this.contactId = data.contact_id;
|
|
2580
|
+
this.persistContactId(data.contact_id);
|
|
2398
2581
|
this.sseTicket = data.sse_ticket;
|
|
2399
2582
|
this.connectSSE(data.sse_ticket);
|
|
2400
2583
|
void this.refreshHistory();
|
|
@@ -2403,6 +2586,51 @@ class AegisChat {
|
|
|
2403
2586
|
this.appendSystem("Sorry — your message could not be delivered. Please try again.");
|
|
2404
2587
|
}
|
|
2405
2588
|
}
|
|
2589
|
+
// ── Media attachments ─────────────────────────────────────────────────────
|
|
2590
|
+
/** Validate + upload a picked file, then send it as a media message. */
|
|
2591
|
+
async handleFile(file) {
|
|
2592
|
+
var _a;
|
|
2593
|
+
const allowed = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp", "application/pdf"]);
|
|
2594
|
+
if (!allowed.has(file.type)) {
|
|
2595
|
+
this.appendSystem("That file type isn’t supported (images or PDF only).");
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
if (file.size > 8 * 1024 * 1024) {
|
|
2599
|
+
this.appendSystem("That file is too large (max 8 MB).");
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
try {
|
|
2603
|
+
const data = await this.fileToBase64(file);
|
|
2604
|
+
const res = await fetch(`${this.apiHost}/v1/chat/upload`, {
|
|
2605
|
+
method: "POST",
|
|
2606
|
+
headers: { "Content-Type": "application/json", "X-Aegis-Write-Key": this.writeKey },
|
|
2607
|
+
body: JSON.stringify({ data, content_type: file.type, filename: file.name })
|
|
2608
|
+
});
|
|
2609
|
+
if (!res.ok) {
|
|
2610
|
+
this.log("upload failed", res.status);
|
|
2611
|
+
this.appendSystem("Sorry — that attachment couldn’t be uploaded.");
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
const out = await res.json();
|
|
2615
|
+
await this.send(((_a = this.input) == null ? void 0 : _a.value) ?? "", { url: out.media_url, type: out.media_type });
|
|
2616
|
+
} catch (err) {
|
|
2617
|
+
this.log("upload error", err);
|
|
2618
|
+
this.appendSystem("Sorry — that attachment couldn’t be uploaded.");
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
/** Read a File as bare base64 (strip the `data:…;base64,` prefix). */
|
|
2622
|
+
fileToBase64(file) {
|
|
2623
|
+
return new Promise((resolve, reject) => {
|
|
2624
|
+
const reader = new FileReader();
|
|
2625
|
+
reader.onload = () => {
|
|
2626
|
+
const result = String(reader.result || "");
|
|
2627
|
+
const comma = result.indexOf(",");
|
|
2628
|
+
resolve(comma >= 0 ? result.slice(comma + 1) : result);
|
|
2629
|
+
};
|
|
2630
|
+
reader.onerror = () => reject(reader.error);
|
|
2631
|
+
reader.readAsDataURL(file);
|
|
2632
|
+
});
|
|
2633
|
+
}
|
|
2406
2634
|
// ── Reply transport: SSE nudge + history refetch (poll fallback) ──────────
|
|
2407
2635
|
connectSSE(ticket) {
|
|
2408
2636
|
if (!this.open) return;
|
|
@@ -2540,6 +2768,21 @@ class AegisChat {
|
|
|
2540
2768
|
void this.send(input.value);
|
|
2541
2769
|
}
|
|
2542
2770
|
});
|
|
2771
|
+
const fileInput = document.createElement("input");
|
|
2772
|
+
fileInput.type = "file";
|
|
2773
|
+
fileInput.accept = "image/*,application/pdf";
|
|
2774
|
+
fileInput.className = "aegis-chat-fileinput";
|
|
2775
|
+
fileInput.addEventListener("change", () => {
|
|
2776
|
+
const f = fileInput.files && fileInput.files[0];
|
|
2777
|
+
if (f) void this.handleFile(f);
|
|
2778
|
+
fileInput.value = "";
|
|
2779
|
+
});
|
|
2780
|
+
const attachBtn = document.createElement("button");
|
|
2781
|
+
attachBtn.type = "button";
|
|
2782
|
+
attachBtn.className = "aegis-chat-attach";
|
|
2783
|
+
attachBtn.setAttribute("aria-label", "Attach a file");
|
|
2784
|
+
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>';
|
|
2785
|
+
attachBtn.addEventListener("click", () => fileInput.click());
|
|
2543
2786
|
const sendBtn = document.createElement("button");
|
|
2544
2787
|
sendBtn.type = "submit";
|
|
2545
2788
|
sendBtn.className = "aegis-chat-send";
|
|
@@ -2549,8 +2792,10 @@ class AegisChat {
|
|
|
2549
2792
|
e.preventDefault();
|
|
2550
2793
|
void this.send(input.value);
|
|
2551
2794
|
});
|
|
2795
|
+
composer.appendChild(attachBtn);
|
|
2552
2796
|
composer.appendChild(input);
|
|
2553
2797
|
composer.appendChild(sendBtn);
|
|
2798
|
+
composer.appendChild(fileInput);
|
|
2554
2799
|
panel.appendChild(header);
|
|
2555
2800
|
panel.appendChild(thread);
|
|
2556
2801
|
panel.appendChild(composer);
|
|
@@ -2570,8 +2815,10 @@ class AegisChat {
|
|
|
2570
2815
|
tip.appendChild(tipClose);
|
|
2571
2816
|
tip.addEventListener("click", () => this.openPanel());
|
|
2572
2817
|
root.appendChild(panel);
|
|
2573
|
-
|
|
2574
|
-
|
|
2818
|
+
if (this.displayMode !== "nav") {
|
|
2819
|
+
root.appendChild(tip);
|
|
2820
|
+
root.appendChild(bubble);
|
|
2821
|
+
}
|
|
2575
2822
|
document.body.appendChild(root);
|
|
2576
2823
|
this.greetingTip = tip;
|
|
2577
2824
|
this.root = root;
|
|
@@ -2609,6 +2856,26 @@ class AegisChat {
|
|
|
2609
2856
|
if (msg.id) this.renderedIds.add(msg.id);
|
|
2610
2857
|
const row = document.createElement("div");
|
|
2611
2858
|
row.className = `aegis-chat-msg aegis-chat-msg--${msg.sender}`;
|
|
2859
|
+
if (msg.media_url) {
|
|
2860
|
+
if (msg.media_type === "image") {
|
|
2861
|
+
const img = document.createElement("img");
|
|
2862
|
+
img.className = "aegis-chat-media-img";
|
|
2863
|
+
img.src = msg.media_url;
|
|
2864
|
+
img.alt = "attachment";
|
|
2865
|
+
img.loading = "lazy";
|
|
2866
|
+
const url = msg.media_url;
|
|
2867
|
+
img.addEventListener("click", () => window.open(url, "_blank", "noopener"));
|
|
2868
|
+
row.appendChild(img);
|
|
2869
|
+
} else {
|
|
2870
|
+
const a = document.createElement("a");
|
|
2871
|
+
a.className = "aegis-chat-media-file";
|
|
2872
|
+
a.href = msg.media_url;
|
|
2873
|
+
a.target = "_blank";
|
|
2874
|
+
a.rel = "noopener noreferrer";
|
|
2875
|
+
a.textContent = "📎 Attachment";
|
|
2876
|
+
row.appendChild(a);
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2612
2879
|
if (msg.content) {
|
|
2613
2880
|
const bubble = document.createElement("div");
|
|
2614
2881
|
bubble.className = "aegis-chat-bubbletext";
|
|
@@ -2700,6 +2967,54 @@ class AegisChat {
|
|
|
2700
2967
|
return void 0;
|
|
2701
2968
|
}
|
|
2702
2969
|
}
|
|
2970
|
+
readStoredContactId() {
|
|
2971
|
+
if (typeof document === "undefined") return void 0;
|
|
2972
|
+
try {
|
|
2973
|
+
return new Storage().get("aegis_chat_cid") ?? void 0;
|
|
2974
|
+
} catch {
|
|
2975
|
+
return void 0;
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
persistContactId(id) {
|
|
2979
|
+
if (!id || typeof document === "undefined") return;
|
|
2980
|
+
try {
|
|
2981
|
+
new Storage().set("aegis_chat_cid", id, 365);
|
|
2982
|
+
} catch {
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
/** Restore a returning visitor's thread on open. Resolves an existing contact
|
|
2986
|
+
* + mints a per-session ticket WITHOUT sending a message (history is ticket-
|
|
2987
|
+
* gated). No-ops for brand-new visitors and once a ticket is already held. */
|
|
2988
|
+
async resumeSession() {
|
|
2989
|
+
if (this.sseTicket) {
|
|
2990
|
+
void this.refreshHistory();
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
if (this.sessionResumed) return;
|
|
2994
|
+
this.sessionResumed = true;
|
|
2995
|
+
if (!this.contactId && !this.anonymousId) return;
|
|
2996
|
+
try {
|
|
2997
|
+
const res = await fetch(`${this.apiHost}/v1/chat/session`, {
|
|
2998
|
+
method: "POST",
|
|
2999
|
+
headers: { "Content-Type": "application/json", "X-Aegis-Write-Key": this.writeKey },
|
|
3000
|
+
body: JSON.stringify({
|
|
3001
|
+
anonymous_id: this.anonymousId,
|
|
3002
|
+
contact_id: this.contactId,
|
|
3003
|
+
channel: this.channel
|
|
3004
|
+
})
|
|
3005
|
+
});
|
|
3006
|
+
if (!res.ok) return;
|
|
3007
|
+
const data = await res.json();
|
|
3008
|
+
if (!data.contact_id || !data.sse_ticket) return;
|
|
3009
|
+
this.contactId = data.contact_id;
|
|
3010
|
+
this.sseTicket = data.sse_ticket;
|
|
3011
|
+
this.persistContactId(data.contact_id);
|
|
3012
|
+
await this.refreshHistory();
|
|
3013
|
+
if (this.open) this.connectSSE(data.sse_ticket);
|
|
3014
|
+
} catch (err) {
|
|
3015
|
+
this.log("resume session failed", err);
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
2703
3018
|
// ── Typing indicator ───────────────────────────────────────────────────────
|
|
2704
3019
|
showTyping() {
|
|
2705
3020
|
if (!this.thread || this.typingEl) return;
|
|
@@ -2800,11 +3115,19 @@ const CHAT_CSS = `
|
|
|
2800
3115
|
.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
3116
|
.aegis-chat-input:focus{border-color:var(--aegis-chat-accent,#4169e1)}
|
|
2802
3117
|
.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}
|
|
3118
|
+
.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}
|
|
3119
|
+
.aegis-chat-attach:hover{background:#f1f5f9;color:var(--aegis-chat-accent,#4169e1)}
|
|
3120
|
+
.aegis-chat-fileinput{display:none}
|
|
3121
|
+
.aegis-chat-media-img{max-width:200px;max-height:200px;border-radius:12px;object-fit:cover;cursor:pointer;display:block}
|
|
3122
|
+
.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}
|
|
3123
|
+
.aegis-chat-msg--customer .aegis-chat-media-file{background:rgba(255,255,255,.18);border-color:transparent;color:#fff}
|
|
2803
3124
|
/* Mobile: raise the bubble above the storefront's full-width cart bar so they
|
|
2804
3125
|
never overlap; widen the panel and reduce the tooltip width. */
|
|
2805
|
-
@media (max-width:
|
|
3126
|
+
@media (max-width:767px){
|
|
2806
3127
|
.aegis-chat-root{bottom:84px;right:16px}
|
|
2807
|
-
|
|
3128
|
+
/* Full-page chat on mobile — the open panel covers the whole viewport (incl.
|
|
3129
|
+
the bottom nav) like a native chat screen, instead of a floating card. */
|
|
3130
|
+
.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
3131
|
.aegis-chat-tip{max-width:200px}
|
|
2809
3132
|
}
|
|
2810
3133
|
`;
|
|
@@ -3376,6 +3699,10 @@ function renderActiveWebChat(ctx, creds) {
|
|
|
3376
3699
|
icon: icon === "sparkle" || icon === "chat" || icon === "logo" ? icon : void 0,
|
|
3377
3700
|
logoUrl: logo ? ctx.sanitizeUrl(logo) ?? void 0 : void 0,
|
|
3378
3701
|
position: ic.chat_position === "bottom-left" ? "bottom-left" : void 0,
|
|
3702
|
+
// 'nav' = headless (no bubble); the host opens it via aegis.chat.open() or a
|
|
3703
|
+
// client_trigger. Defaults to 'bubble'. Per-device override via device_type
|
|
3704
|
+
// targeting on the campaign (e.g. a mobile campaign with chat_display_mode='nav').
|
|
3705
|
+
displayMode: ic.chat_display_mode === "nav" ? "nav" : void 0,
|
|
3379
3706
|
agentPersona: str(ic.chat_agent_persona),
|
|
3380
3707
|
quickReplies: Array.isArray(ic.chat_quick_replies) ? ic.chat_quick_replies.filter((q) => typeof q === "string") : void 0,
|
|
3381
3708
|
// Bridge the engine's session lifecycle to campaign analytics. Per the
|
|
@@ -3436,6 +3763,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
3436
3763
|
constructor(config) {
|
|
3437
3764
|
this.campaigns = [];
|
|
3438
3765
|
this.displayedCampaigns = /* @__PURE__ */ new Set();
|
|
3766
|
+
this._displaySizeMul = 1;
|
|
3439
3767
|
this.suppressedUntil = /* @__PURE__ */ new Map();
|
|
3440
3768
|
this.isInitialized = false;
|
|
3441
3769
|
this.reconnectAttempts = 0;
|
|
@@ -4015,7 +4343,13 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4015
4343
|
const list = eligibleByCategory.get(key);
|
|
4016
4344
|
if (!list || list.length === 0) return;
|
|
4017
4345
|
slot.querySelectorAll(":scope > [data-aegis-slot-default]").forEach((d) => d.remove());
|
|
4018
|
-
const
|
|
4346
|
+
const attrMs = parseInt(slot.getAttribute("data-aegis-slot-rotate") || "0", 10);
|
|
4347
|
+
const declaredMs = list.reduce((m2, c) => {
|
|
4348
|
+
var _a;
|
|
4349
|
+
const v = Number((_a = c.interactive_config) == null ? void 0 : _a.slot_rotate_ms);
|
|
4350
|
+
return Number.isFinite(v) && v > m2 ? v : m2;
|
|
4351
|
+
}, 0);
|
|
4352
|
+
const rotateMs = attrMs > 0 ? attrMs : declaredMs;
|
|
4019
4353
|
if (rotateMs > 0 && list.length > 1) {
|
|
4020
4354
|
this.renderRotatingSlot(slot, list, rotateMs);
|
|
4021
4355
|
} else {
|
|
@@ -4198,6 +4532,13 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4198
4532
|
this.displayedCampaigns.add(campaign.id);
|
|
4199
4533
|
this.addAnimationStyles();
|
|
4200
4534
|
const ic = campaign.interactive_config || {};
|
|
4535
|
+
const inlineMul = this._sizeMul(ic);
|
|
4536
|
+
if (inlineMul !== 1) {
|
|
4537
|
+
const sizer = document.createElement("div");
|
|
4538
|
+
sizer.style.cssText = `transform: scale(${inlineMul}); transform-origin: top center;`;
|
|
4539
|
+
target.appendChild(sizer);
|
|
4540
|
+
target = sizer;
|
|
4541
|
+
}
|
|
4201
4542
|
const { bg, text } = this._surfacePalette(campaign);
|
|
4202
4543
|
const submitUrl = options == null ? void 0 : options.submitUrl;
|
|
4203
4544
|
const _formFields = Array.isArray(ic.form_fields) ? ic.form_fields : null;
|
|
@@ -4214,70 +4555,73 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4214
4555
|
}
|
|
4215
4556
|
return;
|
|
4216
4557
|
}
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4558
|
+
if (campaign.widget_category === "hero") {
|
|
4559
|
+
rendered = renderHeroInline(this.buildRenderContext(campaign), target);
|
|
4560
|
+
} else
|
|
4561
|
+
switch (campaign.sub_type) {
|
|
4562
|
+
case "star_rating":
|
|
4563
|
+
rendered = this.renderStarRatingSlot(
|
|
4564
|
+
campaign,
|
|
4565
|
+
ic,
|
|
4566
|
+
bg,
|
|
4567
|
+
text,
|
|
4568
|
+
target,
|
|
4569
|
+
submitUrl
|
|
4570
|
+
);
|
|
4571
|
+
break;
|
|
4572
|
+
case "nps_survey":
|
|
4573
|
+
rendered = this.renderNPSSurveySlot(
|
|
4574
|
+
campaign,
|
|
4575
|
+
ic,
|
|
4576
|
+
bg,
|
|
4577
|
+
text,
|
|
4578
|
+
target,
|
|
4579
|
+
submitUrl
|
|
4580
|
+
);
|
|
4581
|
+
break;
|
|
4582
|
+
// Parity tracker — built-in embedded gamification renderers (no
|
|
4583
|
+
// AegisMessageRuntime callback needed, so they work on the bill).
|
|
4584
|
+
case "spin_wheel":
|
|
4585
|
+
rendered = this.renderSpinWheelSlot(campaign, ic, bg, text, target);
|
|
4586
|
+
break;
|
|
4587
|
+
case "quick_poll":
|
|
4588
|
+
rendered = this.renderQuickPollSlot(campaign, ic, bg, text, target);
|
|
4589
|
+
break;
|
|
4590
|
+
case "carousel_cards":
|
|
4591
|
+
rendered = this.renderCarouselCardsSlot(campaign, ic, bg, text, target);
|
|
4592
|
+
break;
|
|
4593
|
+
case "countdown_offer":
|
|
4594
|
+
rendered = this.renderCountdownSlot(campaign, ic, bg, text, target);
|
|
4595
|
+
break;
|
|
4596
|
+
case "scratch_card":
|
|
4597
|
+
rendered = this.renderScratchCardSlot(campaign, ic, bg, text, target);
|
|
4598
|
+
break;
|
|
4599
|
+
case "custom_html":
|
|
4600
|
+
rendered = this.renderCustomHtmlSlot(campaign, ic, bg, text, target);
|
|
4601
|
+
break;
|
|
4602
|
+
case "stories":
|
|
4603
|
+
rendered = renderStoriesRings(this.buildRenderContext(campaign), target);
|
|
4604
|
+
break;
|
|
4605
|
+
case "video":
|
|
4606
|
+
rendered = renderVideoInline(this.buildRenderContext(campaign), target);
|
|
4607
|
+
break;
|
|
4608
|
+
case "progress_bar":
|
|
4609
|
+
rendered = renderProgressBarInline(this.buildRenderContext(campaign), target);
|
|
4610
|
+
break;
|
|
4611
|
+
case "quiz":
|
|
4612
|
+
rendered = this.renderQuizSlot(campaign, ic, bg, text, target);
|
|
4613
|
+
if (!rendered) {
|
|
4614
|
+
rendered = this.renderGenericCardSlot(campaign, ic, bg, text, target);
|
|
4615
|
+
}
|
|
4616
|
+
break;
|
|
4617
|
+
// Remaining sub_types (sticky_bar, progress_bar,
|
|
4618
|
+
// product_recommendation) render as a generic card on the bill slot
|
|
4619
|
+
// (overlay path handles their full UX); dedicated slot variants get added
|
|
4620
|
+
// here as merchants need them.
|
|
4621
|
+
default:
|
|
4270
4622
|
rendered = this.renderGenericCardSlot(campaign, ic, bg, text, target);
|
|
4271
|
-
|
|
4272
|
-
|
|
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
|
-
}
|
|
4623
|
+
break;
|
|
4624
|
+
}
|
|
4281
4625
|
if (!rendered) return;
|
|
4282
4626
|
if (!(options == null ? void 0 : options.deferImpression)) {
|
|
4283
4627
|
this.trackEvent(campaign.id, "impression");
|
|
@@ -4580,6 +4924,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4580
4924
|
const labels = shown.map((s) => s.label);
|
|
4581
4925
|
const n = labels.length >= 2 ? labels.length : 8;
|
|
4582
4926
|
const isLossSeg = (i) => !!shown[i] && String(shown[i].prize_type || "").toLowerCase() === "no_prize";
|
|
4927
|
+
const alreadyWon = ic.already_won && typeof ic.already_won === "object" ? ic.already_won : null;
|
|
4583
4928
|
const colorOf = (i) => {
|
|
4584
4929
|
const c = shown[i] && typeof shown[i].color === "string" ? shown[i].color : "";
|
|
4585
4930
|
return c || (i % 2 === 0 ? text : `${text}40`);
|
|
@@ -4615,6 +4960,10 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4615
4960
|
let soundOn = ic.spin_sound_enabled !== false;
|
|
4616
4961
|
const pointerColor = cfgStr("spin_pointer_color") || "#ffffff";
|
|
4617
4962
|
const hubImg = cfgStr("spin_hub_image_url");
|
|
4963
|
+
const wheelBgImg = cfgStr("wheel_background_url");
|
|
4964
|
+
const pointerImg = cfgStr("pointer_image_url");
|
|
4965
|
+
const pointerCenter = (cfgStr("spin_pointer_position") || "top") === "center";
|
|
4966
|
+
const pointerBase = pointerCenter ? "translate(-50%, -100%)" : "translateX(-50%)";
|
|
4618
4967
|
const isUrl = (s) => /^https?:\/\//i.test(s);
|
|
4619
4968
|
const now = () => typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
4620
4969
|
let rotation = 0;
|
|
@@ -4674,7 +5023,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4674
5023
|
vibrate(18);
|
|
4675
5024
|
};
|
|
4676
5025
|
const body = document.createElement("div");
|
|
4677
|
-
body.style.cssText = `padding: ${st.padY ?? 22}px ${st.padX ?? 20}px 20px; display: flex; flex-direction: column; align-items: center; gap:
|
|
5026
|
+
body.style.cssText = `padding: ${st.padY ?? 22}px ${st.padX ?? 20}px 20px; display: flex; flex-direction: column; align-items: center; gap: 10px;`;
|
|
4678
5027
|
if (campaign.title) {
|
|
4679
5028
|
const t = document.createElement("div");
|
|
4680
5029
|
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;`;
|
|
@@ -4692,11 +5041,56 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4692
5041
|
const wheelWrap = document.createElement("div");
|
|
4693
5042
|
wheelWrap.style.cssText = `position: relative; width: ${SIZE}px; height: ${SIZE}px; filter: drop-shadow(0 10px 22px rgba(0,0,0,0.30));`;
|
|
4694
5043
|
const pointer = document.createElement("div");
|
|
4695
|
-
|
|
5044
|
+
if (pointerCenter) {
|
|
5045
|
+
const needleH = Math.round(radius * 0.42);
|
|
5046
|
+
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;`;
|
|
5047
|
+
if (pointerImg) {
|
|
5048
|
+
const pim = document.createElement("img");
|
|
5049
|
+
pim.src = pointerImg;
|
|
5050
|
+
pim.alt = "";
|
|
5051
|
+
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));";
|
|
5052
|
+
pointer.appendChild(pim);
|
|
5053
|
+
} else {
|
|
5054
|
+
const stem = document.createElement("div");
|
|
5055
|
+
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);`;
|
|
5056
|
+
const head = document.createElement("div");
|
|
5057
|
+
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));`;
|
|
5058
|
+
pointer.appendChild(stem);
|
|
5059
|
+
pointer.appendChild(head);
|
|
5060
|
+
}
|
|
5061
|
+
} else if (pointerImg) {
|
|
5062
|
+
pointer.style.cssText = `position: absolute; left: 50%; top: -12px; transform: ${pointerBase}; transform-origin: 50% 0; width: 30px; height: 34px; z-index: 5;`;
|
|
5063
|
+
const pim = document.createElement("img");
|
|
5064
|
+
pim.src = pointerImg;
|
|
5065
|
+
pim.alt = "";
|
|
5066
|
+
pim.style.cssText = "width: 100%; height: 100%; object-fit: contain; display: block; filter: drop-shadow(0 2px 2px rgba(0,0,0,0.35));";
|
|
5067
|
+
pointer.appendChild(pim);
|
|
5068
|
+
} else {
|
|
5069
|
+
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));`;
|
|
5070
|
+
}
|
|
4696
5071
|
const wheel = document.createElement("div");
|
|
4697
|
-
|
|
5072
|
+
const wheelFace = wheelBgImg ? `center / cover no-repeat url("${wheelBgImg}")` : `conic-gradient(${stops})`;
|
|
5073
|
+
const wheelRim = wheelBgImg ? "0" : "5px solid #ffffff";
|
|
5074
|
+
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)`;
|
|
5075
|
+
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"};`;
|
|
4698
5076
|
if (idleAnim === "wobble") wheel.style.animation = "aegisSpinWobble 3s ease-in-out infinite";
|
|
4699
|
-
|
|
5077
|
+
const sliceImgFrac = Math.max(40, Math.min(100, Number(ic.spin_slice_image_size) || 100)) / 100;
|
|
5078
|
+
if (!wheelBgImg) shown.forEach((s, i) => {
|
|
5079
|
+
const simg = typeof s.image_url === "string" ? s.image_url : "";
|
|
5080
|
+
if (!simg || !isUrl(simg)) return;
|
|
5081
|
+
const pts = ["50% 50%"];
|
|
5082
|
+
const steps = Math.max(2, Math.ceil(spans[i] / 6));
|
|
5083
|
+
for (let st2 = 0; st2 <= steps; st2++) {
|
|
5084
|
+
const a = (starts[i] + spans[i] * st2 / steps) * Math.PI / 180;
|
|
5085
|
+
const x = 50 + 50 * sliceImgFrac * Math.sin(a);
|
|
5086
|
+
const y = 50 - 50 * sliceImgFrac * Math.cos(a);
|
|
5087
|
+
pts.push(`${x.toFixed(2)}% ${y.toFixed(2)}%`);
|
|
5088
|
+
}
|
|
5089
|
+
const segEl = document.createElement("div");
|
|
5090
|
+
segEl.style.cssText = `position: absolute; inset: 0; background: center / cover no-repeat url("${simg}"); clip-path: polygon(${pts.join(",")}); pointer-events: none;`;
|
|
5091
|
+
wheel.appendChild(segEl);
|
|
5092
|
+
});
|
|
5093
|
+
if (!wheelBgImg) starts.forEach((b) => {
|
|
4700
5094
|
const ln = document.createElement("div");
|
|
4701
5095
|
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;`;
|
|
4702
5096
|
wheel.appendChild(ln);
|
|
@@ -4705,7 +5099,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4705
5099
|
const rRim = radius - 8;
|
|
4706
5100
|
const bandLen = rRim - rHub;
|
|
4707
5101
|
const rText = (rHub + rRim) / 2;
|
|
4708
|
-
labels.forEach((label, i) => {
|
|
5102
|
+
if (!wheelBgImg) labels.forEach((label, i) => {
|
|
4709
5103
|
const mid = midOf(i);
|
|
4710
5104
|
const icoVal = shown[i] && typeof shown[i].icon === "string" ? shown[i].icon : "";
|
|
4711
5105
|
const chord = 2 * rText * Math.sin(spans[i] * Math.PI / 180 / 2);
|
|
@@ -4750,28 +5144,13 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4750
5144
|
const hub = document.createElement("div");
|
|
4751
5145
|
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;`;
|
|
4752
5146
|
if (hubImg) {
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
hc.style.cssText = "width: 100%; height: 100%;";
|
|
4757
|
-
const hcx = hc.getContext("2d");
|
|
4758
|
-
const him = new Image();
|
|
4759
|
-
him.crossOrigin = "anonymous";
|
|
4760
|
-
him.onload = () => {
|
|
4761
|
-
if (!hcx) return;
|
|
4762
|
-
const ar = him.width / him.height || 1;
|
|
4763
|
-
let w = 32, h = 32;
|
|
4764
|
-
if (ar > 1) h = 32 / ar;
|
|
4765
|
-
else w = 32 * ar;
|
|
4766
|
-
hcx.drawImage(him, (40 - w) / 2, (40 - h) / 2, w, h);
|
|
4767
|
-
hcx.globalCompositeOperation = "source-in";
|
|
4768
|
-
hcx.fillStyle = "#5b626e";
|
|
4769
|
-
hcx.fillRect(0, 0, 40, 40);
|
|
4770
|
-
};
|
|
4771
|
-
him.onerror = () => {
|
|
4772
|
-
};
|
|
5147
|
+
hub.style.border = "none";
|
|
5148
|
+
hub.style.background = "transparent";
|
|
5149
|
+
const him = document.createElement("img");
|
|
4773
5150
|
him.src = hubImg;
|
|
4774
|
-
|
|
5151
|
+
him.alt = "";
|
|
5152
|
+
him.style.cssText = "width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block;";
|
|
5153
|
+
hub.appendChild(him);
|
|
4775
5154
|
} else {
|
|
4776
5155
|
const hubDot = document.createElement("div");
|
|
4777
5156
|
hubDot.style.cssText = `width: 11px; height: 11px; border-radius: 50%; background: ${bg};`;
|
|
@@ -4785,7 +5164,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4785
5164
|
body.appendChild(wheelWrap);
|
|
4786
5165
|
if (!card.style.position) card.style.position = "relative";
|
|
4787
5166
|
const result = document.createElement("div");
|
|
4788
|
-
result.style.cssText = "font-size: 14px; font-weight: 800; min-height:
|
|
5167
|
+
result.style.cssText = "font-size: 14px; font-weight: 800; min-height: 0; text-align: center; letter-spacing: 0.2px;";
|
|
4789
5168
|
if (ic.spin_sound_enabled !== false) {
|
|
4790
5169
|
const soundBtn = document.createElement("button");
|
|
4791
5170
|
soundBtn.type = "button";
|
|
@@ -4800,7 +5179,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4800
5179
|
}
|
|
4801
5180
|
const btn = document.createElement("button");
|
|
4802
5181
|
btn.textContent = campaign.button_text || "Spin the wheel";
|
|
4803
|
-
btn.style.cssText = `padding:
|
|
5182
|
+
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;`;
|
|
4804
5183
|
btn.addEventListener("mouseenter", () => {
|
|
4805
5184
|
if (btn.disabled) return;
|
|
4806
5185
|
btn.style.transform = "translateY(-1px)";
|
|
@@ -4829,12 +5208,12 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4829
5208
|
pVel *= 0.7;
|
|
4830
5209
|
pDefl += pVel;
|
|
4831
5210
|
if (Math.abs(pDefl) > 0.06 || Math.abs(pVel) > 0.06) {
|
|
4832
|
-
pointer.style.transform =
|
|
5211
|
+
pointer.style.transform = `${pointerBase} rotate(${pDefl.toFixed(2)}deg)`;
|
|
4833
5212
|
pointerRaf = requestAnimationFrame(stepPointer);
|
|
4834
5213
|
} else {
|
|
4835
5214
|
pDefl = 0;
|
|
4836
5215
|
pVel = 0;
|
|
4837
|
-
pointer.style.transform =
|
|
5216
|
+
pointer.style.transform = `${pointerBase} rotate(0deg)`;
|
|
4838
5217
|
pointerRaf = 0;
|
|
4839
5218
|
}
|
|
4840
5219
|
};
|
|
@@ -4943,7 +5322,25 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4943
5322
|
this._playReaction(card, "empathize", void 0, ic);
|
|
4944
5323
|
}
|
|
4945
5324
|
if (winReveal === "takeover" && r.won) renderTakeover(r);
|
|
4946
|
-
else
|
|
5325
|
+
else if (r.label && r.code) {
|
|
5326
|
+
result.textContent = "";
|
|
5327
|
+
const tag = document.createElement("span");
|
|
5328
|
+
tag.textContent = `${r.label} — `;
|
|
5329
|
+
const chip = document.createElement("button");
|
|
5330
|
+
chip.type = "button";
|
|
5331
|
+
chip.textContent = `${r.code} ⧉`;
|
|
5332
|
+
chip.style.cssText = `padding: 2px 8px; border-radius: 999px; border: 1.5px dashed ${text}66; background: transparent; color: ${text}; font-weight: 800; font-size: 12px; letter-spacing: 0.5px; cursor: pointer;`;
|
|
5333
|
+
chip.addEventListener("click", () => {
|
|
5334
|
+
var _a;
|
|
5335
|
+
try {
|
|
5336
|
+
void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(r.code));
|
|
5337
|
+
chip.textContent = "Copied ✓";
|
|
5338
|
+
} catch {
|
|
5339
|
+
}
|
|
5340
|
+
});
|
|
5341
|
+
result.appendChild(tag);
|
|
5342
|
+
result.appendChild(chip);
|
|
5343
|
+
} else result.textContent = r.label || "Thanks for playing!";
|
|
4947
5344
|
retireCta();
|
|
4948
5345
|
};
|
|
4949
5346
|
const beginSpin = (velocityDegPerMs) => {
|
|
@@ -4959,16 +5356,40 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4959
5356
|
const v = Math.min(2.4, Math.max(0.25, velocityDegPerMs || 0.6));
|
|
4960
5357
|
const turns = Math.round(3 + v * 2);
|
|
4961
5358
|
const provisional = Math.floor(Math.random() * n);
|
|
5359
|
+
const loopSpeed = 0.5 + v * 0.35;
|
|
5360
|
+
let looping = true;
|
|
4962
5361
|
let done = false;
|
|
5362
|
+
let lastTs = now();
|
|
5363
|
+
const loopFrame = () => {
|
|
5364
|
+
if (!looping) return;
|
|
5365
|
+
const t = now();
|
|
5366
|
+
const moved = loopSpeed * (t - lastTs);
|
|
5367
|
+
lastTs = t;
|
|
5368
|
+
rotation += moved;
|
|
5369
|
+
spinSpeed = moved;
|
|
5370
|
+
wheel.style.transform = `rotate(${rotation}deg)`;
|
|
5371
|
+
playTicks(rotation);
|
|
5372
|
+
requestAnimationFrame(loopFrame);
|
|
5373
|
+
};
|
|
5374
|
+
requestAnimationFrame(loopFrame);
|
|
5375
|
+
const lossIndex = () => {
|
|
5376
|
+
for (let i = 0; i < n; i++) {
|
|
5377
|
+
if (isLossSeg(i)) return i;
|
|
5378
|
+
}
|
|
5379
|
+
return provisional;
|
|
5380
|
+
};
|
|
4963
5381
|
const launch = (idx, r) => {
|
|
4964
5382
|
if (done) return;
|
|
4965
5383
|
done = true;
|
|
5384
|
+
looping = false;
|
|
4966
5385
|
const segMid = midOf(idx);
|
|
4967
5386
|
const want = ((360 - segMid) % 360 + 360) % 360;
|
|
4968
5387
|
const base = rotation;
|
|
4969
5388
|
let target2 = base - (base % 360 + 360) % 360 + want + turns * 360;
|
|
4970
5389
|
if (target2 <= base + 360) target2 += 360;
|
|
4971
|
-
|
|
5390
|
+
const matched = 3 * (target2 - base) / Math.max(0.05, loopSpeed);
|
|
5391
|
+
const duration = Math.min(4200, Math.max(2300, matched));
|
|
5392
|
+
animateTo(target2, duration, () => {
|
|
4972
5393
|
spinning = false;
|
|
4973
5394
|
reveal(r);
|
|
4974
5395
|
});
|
|
@@ -4982,16 +5403,18 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4982
5403
|
const kind = prize && typeof prize.prize_type === "string" ? prize.prize_type.toLowerCase() : shown[idx] && typeof shown[idx].prize_type === "string" ? String(shown[idx].prize_type).toLowerCase() : "";
|
|
4983
5404
|
const lost = prize ? String(prize.prize_type || "").toLowerCase() === "no_prize" : isLossSeg(idx);
|
|
4984
5405
|
launch(idx, { label, code, won: !!label && !lost, kind });
|
|
5406
|
+
}).catch(() => {
|
|
4985
5407
|
});
|
|
4986
|
-
window.setTimeout(
|
|
4987
|
-
()
|
|
4988
|
-
|
|
5408
|
+
window.setTimeout(() => {
|
|
5409
|
+
if (done) return;
|
|
5410
|
+
const li = lossIndex();
|
|
5411
|
+
launch(li, {
|
|
5412
|
+
label: labels[li] || "",
|
|
4989
5413
|
code: "",
|
|
4990
|
-
won:
|
|
4991
|
-
kind: shown[
|
|
4992
|
-
})
|
|
4993
|
-
|
|
4994
|
-
);
|
|
5414
|
+
won: false,
|
|
5415
|
+
kind: shown[li] && typeof shown[li].prize_type === "string" ? String(shown[li].prize_type).toLowerCase() : "no_prize"
|
|
5416
|
+
});
|
|
5417
|
+
}, 8e3);
|
|
4995
5418
|
};
|
|
4996
5419
|
if (spinMode === "button" || spinMode === "both") {
|
|
4997
5420
|
btn.addEventListener("click", () => beginSpin(0.7));
|
|
@@ -5063,6 +5486,16 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5063
5486
|
body.appendChild(btn);
|
|
5064
5487
|
card.appendChild(body);
|
|
5065
5488
|
target.appendChild(card);
|
|
5489
|
+
if (alreadyWon) {
|
|
5490
|
+
stopIdle();
|
|
5491
|
+
const wonKind = String(alreadyWon.prize_type || "").toLowerCase();
|
|
5492
|
+
reveal({
|
|
5493
|
+
label: String(alreadyWon.label || ""),
|
|
5494
|
+
code: String(alreadyWon.coupon_code || ""),
|
|
5495
|
+
won: wonKind !== "no_prize",
|
|
5496
|
+
kind: wonKind
|
|
5497
|
+
});
|
|
5498
|
+
}
|
|
5066
5499
|
return true;
|
|
5067
5500
|
}
|
|
5068
5501
|
/** Built-in embedded QUICK POLL — option buttons submit the chosen index. */
|
|
@@ -5314,6 +5747,34 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5314
5747
|
target.appendChild(card);
|
|
5315
5748
|
return true;
|
|
5316
5749
|
}
|
|
5750
|
+
/** Foil palette — a brushed-metal gradient + a contrast "ink" (for the
|
|
5751
|
+
* SCRATCH-HERE text, sparkle motif, and logo tint) derived from the operator's
|
|
5752
|
+
* chosen foil colour (brand primary / custom hex). Unset / invalid → the
|
|
5753
|
+
* default silver. Light foils get dark ink, dark foils get white ink, so the
|
|
5754
|
+
* prompt stays legible on any colour. */
|
|
5755
|
+
_scratchFoilPalette(hex) {
|
|
5756
|
+
const clean = (hex || "").trim().replace(/^#/, "");
|
|
5757
|
+
if (!/^[0-9a-fA-F]{6}$/.test(clean)) {
|
|
5758
|
+
return { g0: "#d7dbe2", g1: "#b4bac4", g2: "#c9cdd6", ink: "#6b7280" };
|
|
5759
|
+
}
|
|
5760
|
+
const r = parseInt(clean.slice(0, 2), 16);
|
|
5761
|
+
const g = parseInt(clean.slice(2, 4), 16);
|
|
5762
|
+
const b = parseInt(clean.slice(4, 6), 16);
|
|
5763
|
+
const toward = (amt) => {
|
|
5764
|
+
const m2 = (c) => Math.round(c + (255 - c) * amt);
|
|
5765
|
+
return `rgb(${m2(r)}, ${m2(g)}, ${m2(b)})`;
|
|
5766
|
+
};
|
|
5767
|
+
const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
5768
|
+
return {
|
|
5769
|
+
g0: toward(0.34),
|
|
5770
|
+
// bright sheen
|
|
5771
|
+
g1: `#${clean}`,
|
|
5772
|
+
// base colour
|
|
5773
|
+
g2: toward(0.18),
|
|
5774
|
+
// soft sheen
|
|
5775
|
+
ink: lum > 0.62 ? "#5b6270" : "rgba(255,255,255,0.92)"
|
|
5776
|
+
};
|
|
5777
|
+
}
|
|
5317
5778
|
/** Built-in embedded SCRATCH CARD — a canvas foil the customer scratches to
|
|
5318
5779
|
* reveal the server-picked prize, then submits (records + grants). Confetti
|
|
5319
5780
|
* on reveal. No AegisMessageRuntime callback dependency (works on the bill). */
|
|
@@ -5331,6 +5792,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5331
5792
|
let soundOn = ic.scratch_sound_enabled !== false;
|
|
5332
5793
|
const revealImg = cfgStr("reveal_image_url");
|
|
5333
5794
|
const foilImg = cfgStr("scratch_foil_image_url");
|
|
5795
|
+
const foilPal = this._scratchFoilPalette(cfgStr("scratch_foil_color"));
|
|
5334
5796
|
const pool = Array.isArray(ic.prize_pool) ? ic.prize_pool : [];
|
|
5335
5797
|
const isUrl = (s) => /^https?:\/\//i.test(s);
|
|
5336
5798
|
const sampleWin = pool.find(
|
|
@@ -5473,9 +5935,9 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5473
5935
|
if (!ctx) return;
|
|
5474
5936
|
ctx.globalCompositeOperation = "source-over";
|
|
5475
5937
|
const foil = ctx.createLinearGradient(0, 0, CW, CH);
|
|
5476
|
-
foil.addColorStop(0,
|
|
5477
|
-
foil.addColorStop(0.5,
|
|
5478
|
-
foil.addColorStop(1,
|
|
5938
|
+
foil.addColorStop(0, foilPal.g0);
|
|
5939
|
+
foil.addColorStop(0.5, foilPal.g1);
|
|
5940
|
+
foil.addColorStop(1, foilPal.g2);
|
|
5479
5941
|
ctx.fillStyle = foil;
|
|
5480
5942
|
ctx.fillRect(0, 0, CW, CH);
|
|
5481
5943
|
ctx.textAlign = "center";
|
|
@@ -5484,17 +5946,19 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5484
5946
|
ctx.globalAlpha = 0.6;
|
|
5485
5947
|
ctx.drawImage(logo, (CW - logo.width) / 2, CH * 0.42 - logo.height / 2, logo.width, logo.height);
|
|
5486
5948
|
ctx.globalAlpha = 1;
|
|
5487
|
-
ctx.fillStyle =
|
|
5949
|
+
ctx.fillStyle = foilPal.ink;
|
|
5488
5950
|
ctx.font = "700 12px system-ui, sans-serif";
|
|
5489
5951
|
ctx.fillText("SCRATCH HERE", CW / 2, CH * 0.84);
|
|
5490
5952
|
} else {
|
|
5491
|
-
ctx.
|
|
5953
|
+
ctx.globalAlpha = 0.22;
|
|
5954
|
+
ctx.fillStyle = foilPal.ink;
|
|
5492
5955
|
ctx.font = "12px system-ui, sans-serif";
|
|
5493
5956
|
for (let gy = 18; gy < CH; gy += 30) {
|
|
5494
5957
|
const off = (gy / 30 | 0) % 2 ? 16 : 0;
|
|
5495
5958
|
for (let gx = 16 + off; gx < CW; gx += 32) ctx.fillText("✦", gx, gy);
|
|
5496
5959
|
}
|
|
5497
|
-
ctx.
|
|
5960
|
+
ctx.globalAlpha = 1;
|
|
5961
|
+
ctx.fillStyle = foilPal.ink;
|
|
5498
5962
|
ctx.font = "700 13px system-ui, sans-serif";
|
|
5499
5963
|
ctx.fillText("SCRATCH HERE", CW / 2, CH / 2);
|
|
5500
5964
|
}
|
|
@@ -5521,7 +5985,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5521
5985
|
if (!lctx) return;
|
|
5522
5986
|
lctx.drawImage(im, 0, 0, lc.width, lc.height);
|
|
5523
5987
|
lctx.globalCompositeOperation = "source-in";
|
|
5524
|
-
lctx.fillStyle =
|
|
5988
|
+
lctx.fillStyle = foilPal.ink;
|
|
5525
5989
|
lctx.fillRect(0, 0, lc.width, lc.height);
|
|
5526
5990
|
paintFoil(lc);
|
|
5527
5991
|
};
|
|
@@ -6246,6 +6710,9 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
6246
6710
|
return;
|
|
6247
6711
|
}
|
|
6248
6712
|
this.displayedCampaigns.add(campaign.id);
|
|
6713
|
+
this._displaySizeMul = this._sizeMul(
|
|
6714
|
+
campaign.interactive_config
|
|
6715
|
+
);
|
|
6249
6716
|
const interactiveSubTypes = /* @__PURE__ */ new Set([
|
|
6250
6717
|
"spin_wheel",
|
|
6251
6718
|
"scratch_card",
|
|
@@ -6438,6 +6905,14 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
6438
6905
|
* slot renderers (`renderSpinWheelSlot` / `renderScratchCardSlot`) in a modal
|
|
6439
6906
|
* so the gamified widget is fully playable — not a dead frame.
|
|
6440
6907
|
*/
|
|
6908
|
+
/** Display-size multiplier — operator control over how big a bounded overlay
|
|
6909
|
+
* renders (small 0.82 / medium 1 / large 1.18). Default medium = prior size.
|
|
6910
|
+
* Read from interactive_config.display_size; mirrors the schema enum + the
|
|
6911
|
+
* dashboard preview harness so the operator's choice is honored identically. */
|
|
6912
|
+
_sizeMul(ic) {
|
|
6913
|
+
const s = ic && typeof ic.display_size === "string" ? ic.display_size : "medium";
|
|
6914
|
+
return s === "small" ? 0.82 : s === "large" ? 1.18 : 1;
|
|
6915
|
+
}
|
|
6441
6916
|
renderGamificationOverlay(campaign, ic, bg, text) {
|
|
6442
6917
|
const overlay = this.createOverlay(`aegis-in-app-${campaign.sub_type}-overlay`);
|
|
6443
6918
|
const modal = document.createElement("div");
|
|
@@ -6452,7 +6927,11 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
6452
6927
|
this.renderSpinWheelSlot(campaign, ic, bg, text, modal);
|
|
6453
6928
|
}
|
|
6454
6929
|
this._addCornerClose(modal, overlay, campaign.id, text);
|
|
6455
|
-
|
|
6930
|
+
const mul = this._sizeMul(ic);
|
|
6931
|
+
const sizer = document.createElement("div");
|
|
6932
|
+
sizer.style.cssText = `display: flex; justify-content: center; align-items: center; width: 100%; transform: scale(${mul}); transform-origin: center;`;
|
|
6933
|
+
sizer.appendChild(modal);
|
|
6934
|
+
overlay.appendChild(sizer);
|
|
6456
6935
|
this.addAnimationStyles();
|
|
6457
6936
|
document.body.appendChild(overlay);
|
|
6458
6937
|
}
|
|
@@ -7115,8 +7594,30 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7115
7594
|
background: rgba(0,0,0,0.5); display: flex; align-items: center;
|
|
7116
7595
|
justify-content: center; z-index: 99999; animation: aegisFadeIn 0.3s ease;
|
|
7117
7596
|
`;
|
|
7597
|
+
if (this._displaySizeMul !== 1) {
|
|
7598
|
+
const mul = this._displaySizeMul;
|
|
7599
|
+
queueMicrotask(() => {
|
|
7600
|
+
const card = overlay.firstElementChild;
|
|
7601
|
+
if (!card || card.dataset.aegisSized) return;
|
|
7602
|
+
card.dataset.aegisSized = "1";
|
|
7603
|
+
const wrap = document.createElement("div");
|
|
7604
|
+
wrap.style.cssText = `display: flex; align-items: center; justify-content: center; transform: scale(${mul}); transform-origin: center;`;
|
|
7605
|
+
overlay.insertBefore(wrap, card);
|
|
7606
|
+
wrap.appendChild(card);
|
|
7607
|
+
});
|
|
7608
|
+
}
|
|
7118
7609
|
return overlay;
|
|
7119
7610
|
}
|
|
7611
|
+
/** Wrap a card in a display-size scaling container (no-op at medium/×1). Used
|
|
7612
|
+
* by formats that build their overlay inline (renderModal) rather than via
|
|
7613
|
+
* createOverlay, so size is honored identically. */
|
|
7614
|
+
_wrapScaled(card) {
|
|
7615
|
+
if (this._displaySizeMul === 1) return card;
|
|
7616
|
+
const wrap = document.createElement("div");
|
|
7617
|
+
wrap.style.cssText = `display: flex; align-items: center; justify-content: center; transform: scale(${this._displaySizeMul}); transform-origin: center;`;
|
|
7618
|
+
wrap.appendChild(card);
|
|
7619
|
+
return wrap;
|
|
7620
|
+
}
|
|
7120
7621
|
createCTAButton(campaign, bg, text) {
|
|
7121
7622
|
const btn = document.createElement("button");
|
|
7122
7623
|
btn.className = "aegis-cta";
|
|
@@ -7549,7 +8050,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7549
8050
|
actions.appendChild(closeButton);
|
|
7550
8051
|
content.appendChild(actions);
|
|
7551
8052
|
modal.appendChild(content);
|
|
7552
|
-
overlay.appendChild(modal);
|
|
8053
|
+
overlay.appendChild(this._wrapScaled(modal));
|
|
7553
8054
|
if (ic.modal_dismiss_on_overlay !== false) {
|
|
7554
8055
|
overlay.addEventListener("click", (e) => {
|
|
7555
8056
|
if (e.target === overlay) {
|
|
@@ -8985,7 +9486,7 @@ _AegisInAppManager.KNOWN_SURFACES = /* @__PURE__ */ new Set([
|
|
|
8985
9486
|
]);
|
|
8986
9487
|
_AegisInAppManager.SERVED_DEDUP_PREFIX = "aegis_served_fired:";
|
|
8987
9488
|
let AegisInAppManager = _AegisInAppManager;
|
|
8988
|
-
function renderPreview(config) {
|
|
9489
|
+
function renderPreview(config, opts) {
|
|
8989
9490
|
document.querySelectorAll(
|
|
8990
9491
|
'[class^="aegis-in-app-"]'
|
|
8991
9492
|
).forEach((el) => {
|
|
@@ -9009,12 +9510,33 @@ function renderPreview(config) {
|
|
|
9009
9510
|
writeKey: "preview-mode",
|
|
9010
9511
|
apiHost: "",
|
|
9011
9512
|
debugMode: false,
|
|
9012
|
-
enableSSE: false
|
|
9513
|
+
enableSSE: false,
|
|
9514
|
+
...(opts == null ? void 0 : opts.cart) ? { getCartState: () => opts.cart } : {}
|
|
9013
9515
|
});
|
|
9014
9516
|
const m2 = manager;
|
|
9015
9517
|
m2.trackEvent = async () => {
|
|
9016
9518
|
};
|
|
9017
9519
|
m2.addAnimationStyles();
|
|
9520
|
+
const cfgRec = config;
|
|
9521
|
+
const deliveryModes = Array.isArray(cfgRec.delivery_modes) ? cfgRec.delivery_modes : [];
|
|
9522
|
+
const widgetCategory = typeof cfgRec.widget_category === "string" ? cfgRec.widget_category : "";
|
|
9523
|
+
const inline = deliveryModes.includes("embedded_card") && !!widgetCategory;
|
|
9524
|
+
const coGroup = (opts == null ? void 0 : opts.coGroup) ?? [];
|
|
9525
|
+
if (typeof document !== "undefined" && (coGroup.length > 0 || inline)) {
|
|
9526
|
+
const anchors = document.querySelectorAll("[data-aegis-slot]");
|
|
9527
|
+
if (anchors.length > 0) {
|
|
9528
|
+
try {
|
|
9529
|
+
anchors.forEach((slot) => {
|
|
9530
|
+
slot.querySelectorAll(":scope > :not([data-aegis-slot-default])").forEach((el) => el.remove());
|
|
9531
|
+
});
|
|
9532
|
+
const mm = manager;
|
|
9533
|
+
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));
|
|
9534
|
+
mm.renderIntoSlots();
|
|
9535
|
+
if (inline) return;
|
|
9536
|
+
} catch {
|
|
9537
|
+
}
|
|
9538
|
+
}
|
|
9539
|
+
}
|
|
9018
9540
|
if (config.type === "stories" || config.sub_type === "stories") {
|
|
9019
9541
|
const host = document.createElement("div");
|
|
9020
9542
|
host.className = "aegis-in-app-stories-host";
|
|
@@ -9144,10 +9666,19 @@ function maybeStartPairing(apiHost) {
|
|
|
9144
9666
|
}
|
|
9145
9667
|
}
|
|
9146
9668
|
function isIntentLeaf(expr) {
|
|
9147
|
-
return "
|
|
9669
|
+
return !("operands" in expr);
|
|
9670
|
+
}
|
|
9671
|
+
function getValueByPath(namespaces, path) {
|
|
9672
|
+
if (!namespaces || !path) return void 0;
|
|
9673
|
+
let cur = namespaces;
|
|
9674
|
+
for (const part of path.split(".")) {
|
|
9675
|
+
if (cur === null || typeof cur !== "object" || Array.isArray(cur)) return void 0;
|
|
9676
|
+
cur = cur[part];
|
|
9677
|
+
}
|
|
9678
|
+
return cur;
|
|
9148
9679
|
}
|
|
9149
|
-
function evaluateLeaf(leaf, snapshot) {
|
|
9150
|
-
const actual = snapshot[leaf.signal];
|
|
9680
|
+
function evaluateLeaf(leaf, snapshot, namespaces) {
|
|
9681
|
+
const actual = leaf.path !== void 0 ? getValueByPath(namespaces, leaf.path) : leaf.signal !== void 0 ? snapshot[leaf.signal] : void 0;
|
|
9151
9682
|
if (actual === void 0 || actual === null) {
|
|
9152
9683
|
return null;
|
|
9153
9684
|
}
|
|
@@ -9179,14 +9710,14 @@ function evaluateLeaf(leaf, snapshot) {
|
|
|
9179
9710
|
}
|
|
9180
9711
|
}
|
|
9181
9712
|
}
|
|
9182
|
-
function evaluateExpr(expr, snapshot) {
|
|
9713
|
+
function evaluateExpr(expr, snapshot, namespaces) {
|
|
9183
9714
|
if (isIntentLeaf(expr)) {
|
|
9184
|
-
return evaluateLeaf(expr, snapshot);
|
|
9715
|
+
return evaluateLeaf(expr, snapshot, namespaces);
|
|
9185
9716
|
}
|
|
9186
9717
|
switch (expr.op) {
|
|
9187
9718
|
case "AND": {
|
|
9188
9719
|
for (const operand of expr.operands) {
|
|
9189
|
-
const v = evaluateExpr(operand, snapshot);
|
|
9720
|
+
const v = evaluateExpr(operand, snapshot, namespaces);
|
|
9190
9721
|
if (v === null) return false;
|
|
9191
9722
|
if (!v) return false;
|
|
9192
9723
|
}
|
|
@@ -9195,14 +9726,14 @@ function evaluateExpr(expr, snapshot) {
|
|
|
9195
9726
|
case "OR": {
|
|
9196
9727
|
let allNull = true;
|
|
9197
9728
|
for (const operand of expr.operands) {
|
|
9198
|
-
const v = evaluateExpr(operand, snapshot);
|
|
9729
|
+
const v = evaluateExpr(operand, snapshot, namespaces);
|
|
9199
9730
|
if (v === true) return true;
|
|
9200
9731
|
if (v !== null) allNull = false;
|
|
9201
9732
|
}
|
|
9202
9733
|
return allNull ? null : false;
|
|
9203
9734
|
}
|
|
9204
9735
|
case "NOT": {
|
|
9205
|
-
const v = evaluateExpr(expr.operands[0], snapshot);
|
|
9736
|
+
const v = evaluateExpr(expr.operands[0], snapshot, namespaces);
|
|
9206
9737
|
if (v === null) return null;
|
|
9207
9738
|
return !v;
|
|
9208
9739
|
}
|
|
@@ -9211,14 +9742,15 @@ function evaluateExpr(expr, snapshot) {
|
|
|
9211
9742
|
}
|
|
9212
9743
|
}
|
|
9213
9744
|
}
|
|
9214
|
-
function evaluateRule(rule, snapshot) {
|
|
9215
|
-
const result = evaluateExpr(rule.when, snapshot);
|
|
9745
|
+
function evaluateRule(rule, snapshot, namespaces) {
|
|
9746
|
+
const result = evaluateExpr(rule.when, snapshot, namespaces);
|
|
9216
9747
|
return result === true;
|
|
9217
9748
|
}
|
|
9218
9749
|
class IntentRuleEvaluator {
|
|
9219
9750
|
constructor() {
|
|
9220
9751
|
this.armed = [];
|
|
9221
9752
|
this.snapshot = {};
|
|
9753
|
+
this.namespaces = {};
|
|
9222
9754
|
this.firedThisSession = /* @__PURE__ */ new Set();
|
|
9223
9755
|
this.silencedThisSession = /* @__PURE__ */ new Set();
|
|
9224
9756
|
}
|
|
@@ -9232,6 +9764,21 @@ class IntentRuleEvaluator {
|
|
|
9232
9764
|
getSnapshot() {
|
|
9233
9765
|
return this.snapshot;
|
|
9234
9766
|
}
|
|
9767
|
+
// ─── generic reactive context (updateContext) ───
|
|
9768
|
+
/** Merge a namespace's data (shallow). Path-leaves (`cart.total`) resolve
|
|
9769
|
+
* against this. The runtime's updateContext() delegates here so there is ONE
|
|
9770
|
+
* context store, then bridges well-known paths to enum signals. */
|
|
9771
|
+
updateContext(namespace, data) {
|
|
9772
|
+
if (!namespace || !data) return;
|
|
9773
|
+
this.namespaces[namespace] = { ...this.namespaces[namespace], ...data };
|
|
9774
|
+
}
|
|
9775
|
+
getNamespaces() {
|
|
9776
|
+
return this.namespaces;
|
|
9777
|
+
}
|
|
9778
|
+
/** Resolve a dot-path against the live context (for previews / debugging). */
|
|
9779
|
+
getValueByPath(path) {
|
|
9780
|
+
return getValueByPath(this.namespaces, path);
|
|
9781
|
+
}
|
|
9235
9782
|
// ─── armed-campaign management ───
|
|
9236
9783
|
setArmed(campaigns) {
|
|
9237
9784
|
this.armed = [...campaigns];
|
|
@@ -9277,7 +9824,7 @@ class IntentRuleEvaluator {
|
|
|
9277
9824
|
});
|
|
9278
9825
|
for (let i = 0; i < candidates.length; i++) {
|
|
9279
9826
|
const c = candidates[i];
|
|
9280
|
-
if (!evaluateRule(c.rule, this.snapshot)) continue;
|
|
9827
|
+
if (!evaluateRule(c.rule, this.snapshot, this.namespaces)) continue;
|
|
9281
9828
|
if (c.rule.suppress_competing) {
|
|
9282
9829
|
const suppressIds = this.armed.filter((other) => other.id !== c.id).map((other) => other.id);
|
|
9283
9830
|
suppressIds.forEach((id) => this.silencedThisSession.add(id));
|
|
@@ -9360,6 +9907,13 @@ class ContactScoresFetcher {
|
|
|
9360
9907
|
this.latestSnapshot = payload;
|
|
9361
9908
|
if (this.evaluator) {
|
|
9362
9909
|
this.evaluator.updateSnapshot(toIntentSnapshot(payload));
|
|
9910
|
+
if (payload.context && typeof payload.context === "object") {
|
|
9911
|
+
for (const [ns, data] of Object.entries(payload.context)) {
|
|
9912
|
+
if (data && typeof data === "object") {
|
|
9913
|
+
this.evaluator.updateContext(ns, data);
|
|
9914
|
+
}
|
|
9915
|
+
}
|
|
9916
|
+
}
|
|
9363
9917
|
}
|
|
9364
9918
|
return payload;
|
|
9365
9919
|
} catch (err) {
|
|
@@ -12118,6 +12672,9 @@ class AegisLoyaltyManager {
|
|
|
12118
12672
|
}
|
|
12119
12673
|
}
|
|
12120
12674
|
class AegisMessageRuntime {
|
|
12675
|
+
// Context is held in ONE place — `intentRuleEvaluator.namespaces` (fed via
|
|
12676
|
+
// updateContext). The renderer cart reader + the cart_value signal are DERIVED
|
|
12677
|
+
// from it; there is no parallel cart/context store on the runtime.
|
|
12121
12678
|
constructor(config) {
|
|
12122
12679
|
var _a;
|
|
12123
12680
|
this.initialized = false;
|
|
@@ -12156,7 +12713,7 @@ class AegisMessageRuntime {
|
|
|
12156
12713
|
getWorkspaceId: config.getWorkspaceId,
|
|
12157
12714
|
// First-party cart reader — renderers (progress-bar) read live cart
|
|
12158
12715
|
// state from here instead of `window.*` cart globals. See setCartState().
|
|
12159
|
-
getCartState: () => this.
|
|
12716
|
+
getCartState: () => this.deriveCartState()
|
|
12160
12717
|
});
|
|
12161
12718
|
this.intentRuleEvaluator = new IntentRuleEvaluator();
|
|
12162
12719
|
this.contactScores = new ContactScoresFetcher({
|
|
@@ -12237,31 +12794,77 @@ class AegisMessageRuntime {
|
|
|
12237
12794
|
(_b = (_a = this.inApp).onClientEvent) == null ? void 0 : _b.call(_a, eventName, eventData);
|
|
12238
12795
|
}
|
|
12239
12796
|
/**
|
|
12240
|
-
*
|
|
12241
|
-
*
|
|
12242
|
-
*
|
|
12243
|
-
*
|
|
12797
|
+
* Unified reactive context — the GENERAL imperative state API (Plotline-style
|
|
12798
|
+
* "global context state engine"). The host app feeds named namespaces:
|
|
12799
|
+
* runtime.updateContext('cart', { total: 540, items: 3, currency: 'INR' })
|
|
12800
|
+
* runtime.updateContext('user', { tier: 'gold', lifecycle: 'active' })
|
|
12801
|
+
* runtime.updateContext('session', { idle_s: 60 })
|
|
12244
12802
|
*
|
|
12245
|
-
*
|
|
12246
|
-
*
|
|
12247
|
-
*
|
|
12248
|
-
* via the getCartState reader passed into the InAppManager;
|
|
12249
|
-
* 2. feeds the `cart_value` micro-intent signal so `CART_VALUE`-gated
|
|
12250
|
-
* campaigns (e.g. a cart-idle nudge) can evaluate — this signal was
|
|
12251
|
-
* previously never populated client-side;
|
|
12252
|
-
* 3. taps a debounced in-app refresh so a newly eligible cart-gated
|
|
12253
|
-
* campaign arms without waiting for the next identity flip.
|
|
12803
|
+
* Held in PRIVATE runtime state (no `window` pollution). Merged shallowly per
|
|
12804
|
+
* namespace and evaluated entirely client-side — no round-trip. This supersedes
|
|
12805
|
+
* the single-purpose `setCartState` (kept as a thin wrapper for back-compat).
|
|
12254
12806
|
*
|
|
12255
|
-
*
|
|
12256
|
-
*
|
|
12807
|
+
* Design note (vs the proposed rewrite): we DELIBERATELY do NOT introduce a
|
|
12808
|
+
* parallel rule schema. Our canonical `IntentRule` (shared with the cell-plane
|
|
12809
|
+
* Pydantic schema + the editor + seeded campaigns) stays the source of truth.
|
|
12810
|
+
* `updateContext` BRIDGES known paths into the existing enum-keyed
|
|
12811
|
+
* `IntentSnapshot` (e.g. cart.total → the `cart_value` signal) so today's rules
|
|
12812
|
+
* keep firing — fed by the generic API. Arbitrary dot-path leaves
|
|
12813
|
+
* (`cart.foo`) are a forward-compatible follow-up that adds an optional `path`
|
|
12814
|
+
* to the canonical `IntentLeaf` (schema-mirrored + drift-guarded), NOT a second
|
|
12815
|
+
* evaluator. Tri-state (null = unprovable) evaluation is preserved — we do not
|
|
12816
|
+
* regress missing paths to `false`.
|
|
12257
12817
|
*/
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12818
|
+
updateContext(namespace, data) {
|
|
12819
|
+
if (!namespace || typeof namespace !== "string" || !data) return;
|
|
12820
|
+
this.intentRuleEvaluator.updateContext(namespace, data);
|
|
12821
|
+
this.bridgeContextToSignals(namespace);
|
|
12822
|
+
this.inApp.refreshOnEvent("context_updated");
|
|
12823
|
+
}
|
|
12824
|
+
/** Read the current client-side context snapshot (for the editor scrubber /
|
|
12825
|
+
* preview bridge to mirror live values). Read-only view of the ONE store. */
|
|
12826
|
+
getContext() {
|
|
12827
|
+
return this.intentRuleEvaluator.getNamespaces();
|
|
12828
|
+
}
|
|
12829
|
+
/**
|
|
12830
|
+
* Bridge well-known context paths into the canonical enum-keyed IntentSnapshot,
|
|
12831
|
+
* so existing signal-based rules (cart_value) keep firing off the generic
|
|
12832
|
+
* `updateContext`. Reads from the ONE store (the evaluator). Extend here as new
|
|
12833
|
+
* signals map to context paths — no parallel engine.
|
|
12834
|
+
*/
|
|
12835
|
+
bridgeContextToSignals(namespace) {
|
|
12836
|
+
if (namespace === "cart") {
|
|
12837
|
+
const total = Number(this.intentRuleEvaluator.getValueByPath("cart.total") ?? this.intentRuleEvaluator.getValueByPath("cart.cart_value"));
|
|
12838
|
+
if (Number.isFinite(total)) {
|
|
12839
|
+
this.intentRuleEvaluator.updateSignal("cart_value", total);
|
|
12840
|
+
}
|
|
12263
12841
|
}
|
|
12264
|
-
|
|
12842
|
+
}
|
|
12843
|
+
/** Derive the typed cart snapshot for the progress-bar renderer reader from
|
|
12844
|
+
* the ONE context store (the `cart` namespace). No separate cart field. */
|
|
12845
|
+
deriveCartState() {
|
|
12846
|
+
const total = Number(this.intentRuleEvaluator.getValueByPath("cart.total"));
|
|
12847
|
+
if (!Number.isFinite(total)) return void 0;
|
|
12848
|
+
const items = Number(
|
|
12849
|
+
this.intentRuleEvaluator.getValueByPath("cart.items") ?? this.intentRuleEvaluator.getValueByPath("cart.itemCount")
|
|
12850
|
+
);
|
|
12851
|
+
const currency = this.intentRuleEvaluator.getValueByPath("cart.currency");
|
|
12852
|
+
return {
|
|
12853
|
+
total,
|
|
12854
|
+
itemCount: Number.isFinite(items) ? items : 0,
|
|
12855
|
+
currency: typeof currency === "string" ? currency : "INR"
|
|
12856
|
+
};
|
|
12857
|
+
}
|
|
12858
|
+
/**
|
|
12859
|
+
* Back-compat wrapper over `updateContext('cart', …)`. The single-purpose cart
|
|
12860
|
+
* feed for headless storefronts; prefer `updateContext` for new code.
|
|
12861
|
+
*/
|
|
12862
|
+
setCartState(state) {
|
|
12863
|
+
this.updateContext("cart", {
|
|
12864
|
+
total: state.total,
|
|
12865
|
+
items: state.itemCount,
|
|
12866
|
+
currency: state.currency
|
|
12867
|
+
});
|
|
12265
12868
|
}
|
|
12266
12869
|
// --- Qualifying-event refresh facade (DLR Tracker 1 / P1, 2026-05-28) ---
|
|
12267
12870
|
//
|
|
@@ -12442,10 +13045,13 @@ export {
|
|
|
12442
13045
|
TriggerEngine,
|
|
12443
13046
|
U as UserNamespace,
|
|
12444
13047
|
bootstrap,
|
|
13048
|
+
closeChat,
|
|
12445
13049
|
debounce,
|
|
12446
13050
|
aegis as default,
|
|
12447
13051
|
deriveDeviceFingerprint,
|
|
13052
|
+
getCurrentLauncher,
|
|
12448
13053
|
m as murmurhash3_x86_32,
|
|
13054
|
+
openChat,
|
|
12449
13055
|
readFirstPartyCookie,
|
|
12450
13056
|
renderPreview,
|
|
12451
13057
|
throttle,
|