@active-reach/web-sdk 1.22.0 → 1.26.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/chat/AegisChat.d.ts +6 -0
- package/dist/chat/AegisChat.d.ts.map +1 -1
- package/dist/inapp/AegisInAppManager.d.ts +37 -57
- package/dist/inapp/AegisInAppManager.d.ts.map +1 -1
- package/dist/inapp/eval-throttle.d.ts +33 -0
- package/dist/inapp/eval-throttle.d.ts.map +1 -0
- package/dist/inapp/renderers/active-web-chat.d.ts.map +1 -1
- package/dist/inapp/renderers/card-template.d.ts +42 -1
- package/dist/inapp/renderers/card-template.d.ts.map +1 -1
- package/dist/inapp/renderers/carousel-cards.d.ts.map +1 -1
- package/dist/inapp/renderers/games.d.ts.map +1 -1
- package/dist/inapp/renderers/interactive-contract.d.ts +40 -0
- package/dist/inapp/renderers/interactive-contract.d.ts.map +1 -0
- package/dist/inapp/renderers/progress-bar.d.ts.map +1 -1
- package/dist/inapp/renderers/progress-core.d.ts +80 -0
- package/dist/inapp/renderers/progress-core.d.ts.map +1 -0
- package/dist/index.js +664 -692
- package/dist/index.js.map +1 -1
- package/dist/runtime/AegisMessageRuntime.d.ts +1 -1
- package/dist/runtime/AegisMessageRuntime.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -265,6 +265,76 @@ function lucideSvg(name, opts = {}) {
|
|
|
265
265
|
}).join("");
|
|
266
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
267
|
}
|
|
268
|
+
function npsBandFromScore(score) {
|
|
269
|
+
if (score <= 6) return "detractor";
|
|
270
|
+
if (score <= 8) return "passive";
|
|
271
|
+
return "promoter";
|
|
272
|
+
}
|
|
273
|
+
function computeProgress(input) {
|
|
274
|
+
const threshold = input.threshold > 0 ? input.threshold : 0;
|
|
275
|
+
const current = Number.isFinite(input.current) && input.current > 0 ? input.current : 0;
|
|
276
|
+
const pct = threshold > 0 ? Math.max(0, Math.min(100, current / threshold * 100)) : 0;
|
|
277
|
+
const complete = threshold > 0 && pct >= 100;
|
|
278
|
+
const remaining = threshold > 0 ? Math.max(0, threshold - current) : 0;
|
|
279
|
+
const milestonesReached = [];
|
|
280
|
+
if (threshold > 0) {
|
|
281
|
+
if (pct >= 25) milestonesReached.push("q25");
|
|
282
|
+
if (pct >= 50) milestonesReached.push("q50");
|
|
283
|
+
if (pct >= 75) milestonesReached.push("q75");
|
|
284
|
+
if (pct >= 100) milestonesReached.push("unlocked");
|
|
285
|
+
}
|
|
286
|
+
return { pct, complete, remaining, milestonesReached };
|
|
287
|
+
}
|
|
288
|
+
function cardProgressSubLabel(i) {
|
|
289
|
+
const { complete, remaining, pct } = computeProgress(i);
|
|
290
|
+
const threshold = i.threshold > 0 ? i.threshold : 0;
|
|
291
|
+
if (complete) return { text: i.rewardText || "Unlocked!", complete: true };
|
|
292
|
+
if (threshold <= 0) return { text: i.rewardText || "", complete: false };
|
|
293
|
+
const sym = i.goalType === "cart_total" ? i.currencySymbol || "" : "";
|
|
294
|
+
const pctStr = i.showPct ? ` · ${Math.round(pct)}%` : "";
|
|
295
|
+
return {
|
|
296
|
+
text: `Shop for ${sym}${remaining.toFixed(0)} more${pctStr}`,
|
|
297
|
+
complete: false
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function readGoalFromCart(goalType, cart) {
|
|
301
|
+
if (!cart) return void 0;
|
|
302
|
+
if (goalType === "items_in_cart") return Number(cart.itemCount) || 0;
|
|
303
|
+
if (goalType === "cart_total") return Number(cart.total) || 0;
|
|
304
|
+
return void 0;
|
|
305
|
+
}
|
|
306
|
+
function computeCountdown(targetMs, nowMs) {
|
|
307
|
+
const totalMs = Number.isFinite(targetMs) ? Math.max(0, targetMs - nowMs) : 0;
|
|
308
|
+
const expired = !(targetMs > nowMs);
|
|
309
|
+
const totalSec = Math.floor(totalMs / 1e3);
|
|
310
|
+
const days = Math.floor(totalSec / 86400);
|
|
311
|
+
const hours = Math.floor(totalSec % 86400 / 3600);
|
|
312
|
+
const minutes = Math.floor(totalSec % 3600 / 60);
|
|
313
|
+
const seconds = totalSec % 60;
|
|
314
|
+
const pad = (n) => n < 10 ? "0" + n : "" + n;
|
|
315
|
+
const display = days > 0 ? days + "d " + pad(hours) + ":" + pad(minutes) + ":" + pad(seconds) : pad(hours) + ":" + pad(minutes) + ":" + pad(seconds);
|
|
316
|
+
return { totalMs, expired, days, hours, minutes, seconds, display };
|
|
317
|
+
}
|
|
318
|
+
function createFlowAccumulator() {
|
|
319
|
+
const buffer = [];
|
|
320
|
+
let completed = false;
|
|
321
|
+
return {
|
|
322
|
+
recordStep(r) {
|
|
323
|
+
const idx = buffer.findIndex((b) => b.step_id === r.step_id);
|
|
324
|
+
if (idx >= 0) buffer[idx] = r;
|
|
325
|
+
else buffer.push(r);
|
|
326
|
+
},
|
|
327
|
+
steps() {
|
|
328
|
+
return buffer.slice();
|
|
329
|
+
},
|
|
330
|
+
consolidated() {
|
|
331
|
+
return { steps: buffer.slice(), step_count: buffer.length, completed };
|
|
332
|
+
},
|
|
333
|
+
markCompleted() {
|
|
334
|
+
completed = true;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}
|
|
268
338
|
const WEIGHT = {
|
|
269
339
|
regular: "400",
|
|
270
340
|
medium: "500",
|
|
@@ -423,6 +493,337 @@ function contrastFg(bg) {
|
|
|
423
493
|
const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
424
494
|
return lum < 0.5 ? "#ffffff" : "#1f2937";
|
|
425
495
|
}
|
|
496
|
+
function buildCountdownElement(el, fg, muted) {
|
|
497
|
+
const targetStr = typeof el.countdown_target === "string" ? el.countdown_target : "";
|
|
498
|
+
const target = targetStr ? Date.parse(targetStr) : NaN;
|
|
499
|
+
if (!targetStr || isNaN(target)) return null;
|
|
500
|
+
const wrap = document.createElement("div");
|
|
501
|
+
wrap.style.cssText = "display:flex;align-items:baseline;gap:8px;font-variant-numeric:tabular-nums;margin:2px 0;";
|
|
502
|
+
const label = document.createElement("span");
|
|
503
|
+
label.textContent = typeof el.countdown_label === "string" && el.countdown_label || "Ends in";
|
|
504
|
+
label.style.cssText = `font-size:12px;color:${muted};`;
|
|
505
|
+
const time = document.createElement("span");
|
|
506
|
+
time.style.cssText = `font-size:15px;font-weight:700;color:${fg};letter-spacing:0.2px;`;
|
|
507
|
+
wrap.appendChild(label);
|
|
508
|
+
wrap.appendChild(time);
|
|
509
|
+
let started = false;
|
|
510
|
+
let timer = 0;
|
|
511
|
+
const tick = () => {
|
|
512
|
+
if (started && !wrap.isConnected) {
|
|
513
|
+
if (timer) clearInterval(timer);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const cd = computeCountdown(target, Date.now());
|
|
517
|
+
time.textContent = cd.display;
|
|
518
|
+
if (cd.expired && timer) clearInterval(timer);
|
|
519
|
+
started = true;
|
|
520
|
+
};
|
|
521
|
+
tick();
|
|
522
|
+
timer = setInterval(tick, 1e3);
|
|
523
|
+
return wrap;
|
|
524
|
+
}
|
|
525
|
+
function appendRatingThanks(wrap, el, ctx, fg, accent, isHigh) {
|
|
526
|
+
appendThanks(wrap, el, fg, "Thanks for rating us!");
|
|
527
|
+
const url = isHigh && typeof el.rating_high_url === "string" ? ctx.sanitizeUrl(el.rating_high_url) : "";
|
|
528
|
+
if (url) {
|
|
529
|
+
const cta = document.createElement("button");
|
|
530
|
+
cta.type = "button";
|
|
531
|
+
cta.textContent = el.rating_high_cta || "Leave a review";
|
|
532
|
+
cta.style.cssText = `margin:4px auto 0;display:block;padding:9px 18px;border:none;border-radius:999px;background:${accent};color:#fff;font-size:13px;font-weight:800;cursor:pointer;`;
|
|
533
|
+
cta.addEventListener("click", () => {
|
|
534
|
+
ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "rating_input", cta: "high_review" } });
|
|
535
|
+
if (ctx.navigate) ctx.navigate(url, "cta");
|
|
536
|
+
else window.location.href = url;
|
|
537
|
+
});
|
|
538
|
+
wrap.appendChild(cta);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function renderRatingFollowupStep(wrap, el, ctx, stars, fg, accent) {
|
|
542
|
+
while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
|
|
543
|
+
const heading = document.createElement("div");
|
|
544
|
+
heading.textContent = el.rating_followup_prompt || "Sorry to hear that — what went wrong?";
|
|
545
|
+
heading.style.cssText = `font-size:13px;font-weight:700;color:${fg};text-align:center;margin-bottom:8px;`;
|
|
546
|
+
wrap.appendChild(heading);
|
|
547
|
+
const ta = document.createElement("textarea");
|
|
548
|
+
ta.rows = 3;
|
|
549
|
+
ta.placeholder = "Tell us more…";
|
|
550
|
+
ta.style.cssText = `width:100%;box-sizing:border-box;padding:10px;border-radius:10px;border:1px solid color-mix(in srgb, ${accent} 30%, transparent);background:transparent;color:${fg};font-size:13px;resize:vertical;margin-bottom:10px;font-family:inherit;`;
|
|
551
|
+
wrap.appendChild(ta);
|
|
552
|
+
let done = false;
|
|
553
|
+
const finish = (withComment) => {
|
|
554
|
+
var _a, _b;
|
|
555
|
+
if (done) return;
|
|
556
|
+
done = true;
|
|
557
|
+
const comment = withComment ? ta.value.trim() : "";
|
|
558
|
+
void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "rating", comment ? { stars, comment } : { stars }));
|
|
559
|
+
(_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, "acknowledge");
|
|
560
|
+
appendThanks(wrap, el, fg, "Thanks — we’ll do better.");
|
|
561
|
+
};
|
|
562
|
+
const btnRow = document.createElement("div");
|
|
563
|
+
btnRow.style.cssText = "display:flex;gap:8px;justify-content:center;";
|
|
564
|
+
const send = document.createElement("button");
|
|
565
|
+
send.type = "button";
|
|
566
|
+
send.textContent = "Send";
|
|
567
|
+
send.style.cssText = `padding:9px 20px;border-radius:999px;border:none;background:${accent};color:#fff;font-weight:800;font-size:13px;cursor:pointer;`;
|
|
568
|
+
send.addEventListener("click", () => finish(true));
|
|
569
|
+
const skip = document.createElement("button");
|
|
570
|
+
skip.type = "button";
|
|
571
|
+
skip.textContent = "Skip";
|
|
572
|
+
skip.style.cssText = `padding:9px 16px;border-radius:999px;border:1px solid color-mix(in srgb, ${accent} 30%, transparent);background:transparent;color:${fg};font-weight:600;font-size:13px;cursor:pointer;`;
|
|
573
|
+
skip.addEventListener("click", () => finish(false));
|
|
574
|
+
btnRow.appendChild(send);
|
|
575
|
+
btnRow.appendChild(skip);
|
|
576
|
+
wrap.appendChild(btnRow);
|
|
577
|
+
ta.focus();
|
|
578
|
+
}
|
|
579
|
+
function buildRatingInputElement(el, ctx, fg) {
|
|
580
|
+
const accent = ctx.sanitizeColor(ctx.campaign.background_color || "#4169e1");
|
|
581
|
+
const wrap = document.createElement("div");
|
|
582
|
+
wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
|
|
583
|
+
if (el.content) {
|
|
584
|
+
const q = document.createElement("div");
|
|
585
|
+
q.textContent = el.content;
|
|
586
|
+
q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
|
|
587
|
+
wrap.appendChild(q);
|
|
588
|
+
}
|
|
589
|
+
const scale = typeof el.rating_scale === "number" && el.rating_scale >= 2 && el.rating_scale <= 10 ? Math.floor(el.rating_scale) : 5;
|
|
590
|
+
const row = document.createElement("div");
|
|
591
|
+
row.style.cssText = "display:flex;gap:6px;";
|
|
592
|
+
const stars = [];
|
|
593
|
+
let submitted = false;
|
|
594
|
+
const paint = (upto) => {
|
|
595
|
+
stars.forEach((s, idx) => {
|
|
596
|
+
const on = idx < upto;
|
|
597
|
+
s.setAttribute("stroke", on ? "#f59e0b" : "#d1d5db");
|
|
598
|
+
s.setAttribute("fill", on ? "#f59e0b" : "none");
|
|
599
|
+
});
|
|
600
|
+
};
|
|
601
|
+
for (let i = 1; i <= scale; i++) {
|
|
602
|
+
const btn = document.createElement("button");
|
|
603
|
+
btn.type = "button";
|
|
604
|
+
btn.setAttribute("aria-label", `${i} star${i > 1 ? "s" : ""}`);
|
|
605
|
+
btn.style.cssText = "cursor:pointer;border:none;background:none;padding:0;line-height:0;";
|
|
606
|
+
const svg = lucideSvgEl("star", { size: 26, color: "#d1d5db" });
|
|
607
|
+
if (svg) {
|
|
608
|
+
btn.appendChild(svg);
|
|
609
|
+
stars.push(svg);
|
|
610
|
+
}
|
|
611
|
+
btn.addEventListener("mouseenter", () => {
|
|
612
|
+
if (!submitted) paint(i);
|
|
613
|
+
});
|
|
614
|
+
btn.addEventListener("mouseleave", () => {
|
|
615
|
+
if (!submitted) paint(0);
|
|
616
|
+
});
|
|
617
|
+
btn.addEventListener("click", () => {
|
|
618
|
+
var _a, _b;
|
|
619
|
+
if (submitted) return;
|
|
620
|
+
submitted = true;
|
|
621
|
+
paint(i);
|
|
622
|
+
ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "rating_input", stars: i } });
|
|
623
|
+
const lowThreshold = typeof el.rating_followup_threshold === "number" ? el.rating_followup_threshold : Math.ceil(scale * 0.8);
|
|
624
|
+
if (el.rating_followup_enabled === true && i <= lowThreshold) {
|
|
625
|
+
renderRatingFollowupStep(wrap, el, ctx, i, fg, accent);
|
|
626
|
+
} else {
|
|
627
|
+
void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "rating", { stars: i }));
|
|
628
|
+
(_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, i >= Math.ceil(scale * 0.6) ? "affirm" : "acknowledge");
|
|
629
|
+
appendRatingThanks(wrap, el, ctx, fg, accent, i >= Math.ceil(scale * 0.6));
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
row.appendChild(btn);
|
|
633
|
+
}
|
|
634
|
+
wrap.appendChild(row);
|
|
635
|
+
return wrap;
|
|
636
|
+
}
|
|
637
|
+
function appendThanks(wrap, el, fg, fallback) {
|
|
638
|
+
const msg = typeof el.thanks_text === "string" && el.thanks_text.trim() ? el.thanks_text : fallback;
|
|
639
|
+
if (!msg) return;
|
|
640
|
+
const note = document.createElement("div");
|
|
641
|
+
note.textContent = msg;
|
|
642
|
+
note.setAttribute("role", "status");
|
|
643
|
+
note.style.cssText = `font-size:12px;font-weight:600;color:${fg};opacity:0.85;margin-top:4px;`;
|
|
644
|
+
wrap.appendChild(note);
|
|
645
|
+
}
|
|
646
|
+
function buildPollElement(el, ctx, fg, accent) {
|
|
647
|
+
const opts = Array.isArray(el.poll_options) ? el.poll_options.filter((o) => typeof o === "string" && o.trim().length > 0) : [];
|
|
648
|
+
if (opts.length < 2) return null;
|
|
649
|
+
const wrap = document.createElement("div");
|
|
650
|
+
wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
|
|
651
|
+
if (el.content) {
|
|
652
|
+
const q = document.createElement("div");
|
|
653
|
+
q.textContent = el.content;
|
|
654
|
+
q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
|
|
655
|
+
wrap.appendChild(q);
|
|
656
|
+
}
|
|
657
|
+
let submitted = false;
|
|
658
|
+
opts.forEach((label, idx) => {
|
|
659
|
+
const btn = document.createElement("button");
|
|
660
|
+
btn.type = "button";
|
|
661
|
+
btn.textContent = label;
|
|
662
|
+
btn.style.cssText = `text-align:left;cursor:pointer;border:1px solid color-mix(in srgb, ${accent} 35%, transparent);background:transparent;color:${fg};border-radius:10px;padding:9px 12px;font-size:13px;font-weight:600;transition:background .12s,border-color .12s;`;
|
|
663
|
+
btn.addEventListener("mouseenter", () => {
|
|
664
|
+
if (!submitted) btn.style.background = `color-mix(in srgb, ${accent} 8%, transparent)`;
|
|
665
|
+
});
|
|
666
|
+
btn.addEventListener("mouseleave", () => {
|
|
667
|
+
if (!submitted) btn.style.background = "transparent";
|
|
668
|
+
});
|
|
669
|
+
btn.addEventListener("click", () => {
|
|
670
|
+
var _a, _b;
|
|
671
|
+
if (submitted) return;
|
|
672
|
+
submitted = true;
|
|
673
|
+
Array.from(wrap.querySelectorAll("button")).forEach((b) => {
|
|
674
|
+
b.disabled = true;
|
|
675
|
+
b.style.opacity = "0.6";
|
|
676
|
+
});
|
|
677
|
+
btn.style.background = accent;
|
|
678
|
+
btn.style.color = "#fff";
|
|
679
|
+
btn.style.opacity = "1";
|
|
680
|
+
ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "poll", option_index: idx, option_label: label } });
|
|
681
|
+
void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "poll", { option_index: idx, option_label: label }));
|
|
682
|
+
(_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, "affirm");
|
|
683
|
+
appendThanks(wrap, el, fg, "Thanks for your answer!");
|
|
684
|
+
});
|
|
685
|
+
wrap.appendChild(btn);
|
|
686
|
+
});
|
|
687
|
+
return wrap;
|
|
688
|
+
}
|
|
689
|
+
function appendNpsThanks(wrap, el, ctx, fg, accent, isPromoter) {
|
|
690
|
+
while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
|
|
691
|
+
const note = document.createElement("div");
|
|
692
|
+
note.setAttribute("role", "status");
|
|
693
|
+
note.textContent = (typeof el.thanks_text === "string" && el.thanks_text.trim() ? el.thanks_text : "") || (isPromoter ? "Thank you! We’d love a public review." : "Thanks — your feedback helps us improve.");
|
|
694
|
+
note.style.cssText = `font-size:13px;font-weight:700;color:${fg};text-align:center;margin:6px 0;`;
|
|
695
|
+
wrap.appendChild(note);
|
|
696
|
+
const url = isPromoter && typeof el.nps_promoter_url === "string" ? ctx.sanitizeUrl(el.nps_promoter_url) : "";
|
|
697
|
+
if (url) {
|
|
698
|
+
const cta = document.createElement("button");
|
|
699
|
+
cta.type = "button";
|
|
700
|
+
cta.textContent = el.nps_promoter_cta || "Leave a review";
|
|
701
|
+
cta.style.cssText = `margin:4px auto 0;display:block;padding:9px 18px;border:none;border-radius:999px;background:${accent};color:#fff;font-size:13px;font-weight:800;cursor:pointer;`;
|
|
702
|
+
cta.addEventListener("click", () => {
|
|
703
|
+
ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "nps", cta: "promoter_review" } });
|
|
704
|
+
if (ctx.navigate) ctx.navigate(url, "cta");
|
|
705
|
+
else window.location.href = url;
|
|
706
|
+
});
|
|
707
|
+
wrap.appendChild(cta);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function renderNpsReasonStep(wrap, el, ctx, score, fg, accent) {
|
|
711
|
+
const promoterThreshold = typeof el.nps_promoter_threshold === "number" ? el.nps_promoter_threshold : 9;
|
|
712
|
+
const band = npsBandFromScore(score);
|
|
713
|
+
const prompt = score >= promoterThreshold ? el.nps_follow_up_promoter || "What do you love most about us?" : score <= 6 ? el.nps_follow_up_detractor || "How can we improve?" : el.nps_follow_up_passive || "What would make us a 10?";
|
|
714
|
+
while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
|
|
715
|
+
const heading = document.createElement("div");
|
|
716
|
+
heading.textContent = prompt;
|
|
717
|
+
heading.style.cssText = `font-size:13px;font-weight:700;color:${fg};text-align:center;margin-bottom:8px;`;
|
|
718
|
+
wrap.appendChild(heading);
|
|
719
|
+
const ta = document.createElement("textarea");
|
|
720
|
+
const chips = Array.isArray(el.nps_reason_chips) ? el.nps_reason_chips : [];
|
|
721
|
+
if (chips.length) {
|
|
722
|
+
const chipRow = document.createElement("div");
|
|
723
|
+
chipRow.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-bottom:8px;";
|
|
724
|
+
chips.slice(0, 8).forEach((c) => {
|
|
725
|
+
const chip = document.createElement("button");
|
|
726
|
+
chip.type = "button";
|
|
727
|
+
chip.textContent = c;
|
|
728
|
+
chip.style.cssText = `padding:5px 11px;border-radius:999px;border:1px solid color-mix(in srgb, ${accent} 35%, transparent);background:transparent;color:${fg};font-size:12px;cursor:pointer;`;
|
|
729
|
+
chip.addEventListener("click", () => {
|
|
730
|
+
ta.value = ta.value ? `${ta.value}, ${c}` : c;
|
|
731
|
+
ta.focus();
|
|
732
|
+
});
|
|
733
|
+
chipRow.appendChild(chip);
|
|
734
|
+
});
|
|
735
|
+
wrap.appendChild(chipRow);
|
|
736
|
+
}
|
|
737
|
+
ta.rows = 3;
|
|
738
|
+
ta.placeholder = "Share a little more…";
|
|
739
|
+
ta.style.cssText = `width:100%;box-sizing:border-box;padding:10px;border-radius:10px;border:1px solid color-mix(in srgb, ${accent} 30%, transparent);background:transparent;color:${fg};font-size:13px;resize:vertical;margin-bottom:10px;font-family:inherit;`;
|
|
740
|
+
wrap.appendChild(ta);
|
|
741
|
+
let done = false;
|
|
742
|
+
const finish = (withComment) => {
|
|
743
|
+
var _a, _b;
|
|
744
|
+
if (done) return;
|
|
745
|
+
done = true;
|
|
746
|
+
const comment = withComment ? ta.value.trim() : "";
|
|
747
|
+
void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "nps", comment ? { score, band, comment } : { score, band }));
|
|
748
|
+
(_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, score >= promoterThreshold ? "affirm" : score <= 6 ? "empathize" : "acknowledge");
|
|
749
|
+
appendNpsThanks(wrap, el, ctx, fg, accent, score >= promoterThreshold);
|
|
750
|
+
};
|
|
751
|
+
const btnRow = document.createElement("div");
|
|
752
|
+
btnRow.style.cssText = "display:flex;gap:8px;justify-content:center;";
|
|
753
|
+
const send = document.createElement("button");
|
|
754
|
+
send.type = "button";
|
|
755
|
+
send.textContent = "Send";
|
|
756
|
+
send.style.cssText = `padding:9px 20px;border-radius:999px;border:none;background:${accent};color:#fff;font-weight:800;font-size:13px;cursor:pointer;`;
|
|
757
|
+
send.addEventListener("click", () => finish(true));
|
|
758
|
+
const skip = document.createElement("button");
|
|
759
|
+
skip.type = "button";
|
|
760
|
+
skip.textContent = "Skip";
|
|
761
|
+
skip.style.cssText = `padding:9px 16px;border-radius:999px;border:1px solid color-mix(in srgb, ${accent} 30%, transparent);background:transparent;color:${fg};font-weight:600;font-size:13px;cursor:pointer;`;
|
|
762
|
+
skip.addEventListener("click", () => finish(false));
|
|
763
|
+
btnRow.appendChild(send);
|
|
764
|
+
btnRow.appendChild(skip);
|
|
765
|
+
wrap.appendChild(btnRow);
|
|
766
|
+
ta.focus();
|
|
767
|
+
}
|
|
768
|
+
function buildNpsElement(el, ctx, fg, accent, muted) {
|
|
769
|
+
const wrap = document.createElement("div");
|
|
770
|
+
wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
|
|
771
|
+
if (el.content) {
|
|
772
|
+
const q = document.createElement("div");
|
|
773
|
+
q.textContent = el.content;
|
|
774
|
+
q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
|
|
775
|
+
wrap.appendChild(q);
|
|
776
|
+
}
|
|
777
|
+
const row = document.createElement("div");
|
|
778
|
+
row.style.cssText = "display:flex;flex-wrap:wrap;gap:4px;";
|
|
779
|
+
let submitted = false;
|
|
780
|
+
for (let n = 0; n <= 10; n++) {
|
|
781
|
+
const btn = document.createElement("button");
|
|
782
|
+
btn.type = "button";
|
|
783
|
+
btn.textContent = String(n);
|
|
784
|
+
btn.setAttribute("aria-label", `Score ${n}`);
|
|
785
|
+
btn.style.cssText = `cursor:pointer;min-width:26px;height:30px;border:1px solid color-mix(in srgb, ${accent} 30%, transparent);background:transparent;color:${fg};border-radius:7px;font-size:12px;font-weight:700;transition:background .12s;`;
|
|
786
|
+
btn.addEventListener("mouseenter", () => {
|
|
787
|
+
if (!submitted) btn.style.background = `color-mix(in srgb, ${accent} 10%, transparent)`;
|
|
788
|
+
});
|
|
789
|
+
btn.addEventListener("mouseleave", () => {
|
|
790
|
+
if (!submitted) btn.style.background = "transparent";
|
|
791
|
+
});
|
|
792
|
+
btn.addEventListener("click", () => {
|
|
793
|
+
var _a, _b;
|
|
794
|
+
if (submitted) return;
|
|
795
|
+
submitted = true;
|
|
796
|
+
Array.from(row.children).forEach((b) => {
|
|
797
|
+
b.disabled = true;
|
|
798
|
+
b.style.opacity = "0.55";
|
|
799
|
+
});
|
|
800
|
+
btn.style.background = accent;
|
|
801
|
+
btn.style.color = "#fff";
|
|
802
|
+
btn.style.opacity = "1";
|
|
803
|
+
const npsPayload = { score: n, band: npsBandFromScore(n) };
|
|
804
|
+
ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "nps", score: n, band: npsPayload.band } });
|
|
805
|
+
if (el.nps_followup_enabled === true) {
|
|
806
|
+
renderNpsReasonStep(wrap, el, ctx, n, fg, accent);
|
|
807
|
+
} else {
|
|
808
|
+
void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "nps", { ...npsPayload }));
|
|
809
|
+
(_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, n >= 9 ? "affirm" : n >= 7 ? "acknowledge" : "empathize");
|
|
810
|
+
appendThanks(wrap, el, fg, "Thanks for your feedback!");
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
row.appendChild(btn);
|
|
814
|
+
}
|
|
815
|
+
wrap.appendChild(row);
|
|
816
|
+
const scale = document.createElement("div");
|
|
817
|
+
scale.style.cssText = `display:flex;justify-content:space-between;font-size:10px;color:${muted};`;
|
|
818
|
+
const lo = document.createElement("span");
|
|
819
|
+
lo.textContent = "Not likely";
|
|
820
|
+
const hi = document.createElement("span");
|
|
821
|
+
hi.textContent = "Very likely";
|
|
822
|
+
scale.appendChild(lo);
|
|
823
|
+
scale.appendChild(hi);
|
|
824
|
+
wrap.appendChild(scale);
|
|
825
|
+
return wrap;
|
|
826
|
+
}
|
|
426
827
|
function buildProgressElement(el, ctx, accent, muted) {
|
|
427
828
|
const { trackEvent, campaign, sanitizeColor } = ctx;
|
|
428
829
|
const goal = el.goal_type || "cart_total";
|
|
@@ -445,9 +846,8 @@ function buildProgressElement(el, ctx, accent, muted) {
|
|
|
445
846
|
return 0;
|
|
446
847
|
};
|
|
447
848
|
const readCur = () => {
|
|
448
|
-
const
|
|
449
|
-
if (
|
|
450
|
-
if (goal === "cart_total") return Number(cart == null ? void 0 : cart.total) || 0;
|
|
849
|
+
const fromCart = readGoalFromCart(goal, ctx.getCartState && ctx.getCartState());
|
|
850
|
+
if (fromCart !== void 0) return fromCart;
|
|
451
851
|
const p = props();
|
|
452
852
|
if (goal === "loyalty_points") return Number(p["loyalty.points"]) || 0;
|
|
453
853
|
if (goal === "referrals") return Number(p["referrals.count"]) || 0;
|
|
@@ -466,23 +866,32 @@ function buildProgressElement(el, ctx, accent, muted) {
|
|
|
466
866
|
wrap.appendChild(shell);
|
|
467
867
|
let firedUnlock = false;
|
|
468
868
|
const update = () => {
|
|
869
|
+
var _a;
|
|
469
870
|
const th = threshold();
|
|
470
871
|
const cur = readCur();
|
|
471
|
-
const pct
|
|
872
|
+
const { pct, complete } = computeProgress({ threshold: th, current: cur });
|
|
472
873
|
bar.style.width = `${pct}%`;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
874
|
+
const labeled = cardProgressSubLabel({
|
|
875
|
+
goalType: goal,
|
|
876
|
+
threshold: th,
|
|
877
|
+
current: cur,
|
|
878
|
+
rewardText: el.reward_text,
|
|
879
|
+
currencySymbol: moneyGoal ? sym() : "",
|
|
880
|
+
showPct: el.show_pct
|
|
881
|
+
});
|
|
882
|
+
sub.textContent = labeled.text;
|
|
883
|
+
if (complete) {
|
|
884
|
+
sub.style.color = "#10b981";
|
|
885
|
+
sub.style.fontWeight = "700";
|
|
476
886
|
if (!firedUnlock) {
|
|
477
887
|
firedUnlock = true;
|
|
478
888
|
trackEvent(campaign.id, "clicked", { stepId: el.id || "unlocked" });
|
|
889
|
+
if (el.celebrate_on_complete !== false) (_a = ctx.playReaction) == null ? void 0 : _a.call(ctx, wrap, "celebrate");
|
|
479
890
|
}
|
|
480
891
|
} else {
|
|
481
892
|
sub.style.color = muted;
|
|
482
|
-
|
|
483
|
-
|
|
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 || "";
|
|
893
|
+
sub.style.fontWeight = "";
|
|
894
|
+
firedUnlock = false;
|
|
486
895
|
}
|
|
487
896
|
};
|
|
488
897
|
update();
|
|
@@ -516,6 +925,7 @@ function renderCardFromTemplate(card, opts) {
|
|
|
516
925
|
border:1px solid ${frame.border ? sanitizeColor(frame.border) : "transparent"};
|
|
517
926
|
border-radius:${radius}; padding:8px;
|
|
518
927
|
${frame.max_width ? `max-width:${frame.max_width}px;` : ""}${frame.min_width ? `min-width:${frame.min_width}px;` : ""}
|
|
928
|
+
${frame.height ? `height:${frame.height}px;` : ""}${frame.min_height ? `min-height:${frame.min_height}px;` : ""}
|
|
519
929
|
${sizing}
|
|
520
930
|
cursor:${card.cta_url || inline ? "pointer" : "default"}; transition:transform 0.15s ease;
|
|
521
931
|
`;
|
|
@@ -638,6 +1048,24 @@ function renderCardFromTemplate(card, opts) {
|
|
|
638
1048
|
container.appendChild(buildProgressElement(el, ctx, brand, muted));
|
|
639
1049
|
break;
|
|
640
1050
|
}
|
|
1051
|
+
case "countdown": {
|
|
1052
|
+
const cd = buildCountdownElement(el, fg, muted);
|
|
1053
|
+
if (cd) container.appendChild(cd);
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
case "rating_input": {
|
|
1057
|
+
container.appendChild(buildRatingInputElement(el, ctx, fg));
|
|
1058
|
+
break;
|
|
1059
|
+
}
|
|
1060
|
+
case "poll": {
|
|
1061
|
+
const p = buildPollElement(el, ctx, fg, brand);
|
|
1062
|
+
if (p) container.appendChild(p);
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
case "nps": {
|
|
1066
|
+
container.appendChild(buildNpsElement(el, ctx, fg, brand, muted));
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
641
1069
|
case "group": {
|
|
642
1070
|
const g = document.createElement("div");
|
|
643
1071
|
const gdir = el.layout === "row" ? "row" : "column";
|
|
@@ -790,6 +1218,9 @@ function renderCarouselCards(ctx) {
|
|
|
790
1218
|
layoutAccent: accent
|
|
791
1219
|
});
|
|
792
1220
|
tile.setAttribute("data-card-index", String(i));
|
|
1221
|
+
tile.addEventListener("click", () => {
|
|
1222
|
+
trackEvent(campaign.id, "clicked", { stepId: `card_${i}` });
|
|
1223
|
+
});
|
|
793
1224
|
track.appendChild(tile);
|
|
794
1225
|
});
|
|
795
1226
|
card.appendChild(track);
|
|
@@ -1090,21 +1521,23 @@ function buildProgressBar(ctx, inline) {
|
|
|
1090
1521
|
};
|
|
1091
1522
|
const update = () => {
|
|
1092
1523
|
const cur = readCurrentValue();
|
|
1093
|
-
const pct
|
|
1524
|
+
const { pct, complete, remaining, milestonesReached } = computeProgress({
|
|
1525
|
+
threshold,
|
|
1526
|
+
current: cur
|
|
1527
|
+
});
|
|
1094
1528
|
fillEl.style.width = `${pct}%`;
|
|
1095
1529
|
numeric.textContent = `${cur.toFixed(0)} / ${threshold.toFixed(0)} · ${Math.round(pct)}%`;
|
|
1096
|
-
shimmer.style.display =
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
if (
|
|
1530
|
+
shimmer.style.display = complete ? "none" : "";
|
|
1531
|
+
for (const m2 of milestonesReached) {
|
|
1532
|
+
if (m2 !== "unlocked") fireMilestone(m2);
|
|
1533
|
+
}
|
|
1534
|
+
if (complete) {
|
|
1101
1535
|
footnote.textContent = rewardText;
|
|
1102
1536
|
if (!lastFiredUnlock) {
|
|
1103
1537
|
lastFiredUnlock = true;
|
|
1104
1538
|
trackEvent(campaign.id, "clicked", { stepId: "unlocked" });
|
|
1105
1539
|
}
|
|
1106
1540
|
} else {
|
|
1107
|
-
const remaining = Math.max(0, threshold - cur);
|
|
1108
1541
|
footnote.textContent = `Add ${remaining.toFixed(0)} more to unlock: ${rewardText}`;
|
|
1109
1542
|
}
|
|
1110
1543
|
};
|
|
@@ -2023,6 +2456,9 @@ function injectStoryStyles() {
|
|
|
2023
2456
|
`;
|
|
2024
2457
|
document.head.appendChild(style);
|
|
2025
2458
|
}
|
|
2459
|
+
function earnPct(current, threshold) {
|
|
2460
|
+
return computeProgress({ threshold, current }).pct;
|
|
2461
|
+
}
|
|
2026
2462
|
const num = (v, d) => {
|
|
2027
2463
|
const n = Number(v);
|
|
2028
2464
|
return Number.isFinite(n) ? n : d;
|
|
@@ -2065,7 +2501,8 @@ function openGameModal(ctx, className, maxWidth = 360) {
|
|
|
2065
2501
|
};
|
|
2066
2502
|
const close = document.createElement("button");
|
|
2067
2503
|
close.type = "button";
|
|
2068
|
-
|
|
2504
|
+
const closeIcon = lucideSvgEl("x", { size: 15 });
|
|
2505
|
+
if (closeIcon) close.appendChild(closeIcon);
|
|
2069
2506
|
close.setAttribute("aria-label", "Close");
|
|
2070
2507
|
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;`;
|
|
2071
2508
|
close.addEventListener("click", () => dismiss(true));
|
|
@@ -2157,7 +2594,7 @@ function renderMilestone(ctx) {
|
|
|
2157
2594
|
const cur = num(ic.milestone_current, 1200);
|
|
2158
2595
|
const target = num(ic.milestone_target, 2e3);
|
|
2159
2596
|
const steps = arr(ic.milestone_steps);
|
|
2160
|
-
const pct =
|
|
2597
|
+
const pct = earnPct(cur, target);
|
|
2161
2598
|
s.content.appendChild(gEl("div", "font-size:13px;opacity:0.8;margin-bottom:2px", `${unit}${cur} of ${unit}${target}`));
|
|
2162
2599
|
s.content.appendChild(fillBar(s.accent, pct));
|
|
2163
2600
|
s.content.appendChild(gEl("div", "font-size:13px;font-weight:600", `${unit}${Math.max(0, target - cur)} to go`));
|
|
@@ -2178,7 +2615,7 @@ function renderChallenge(ctx) {
|
|
|
2178
2615
|
const s = openGameModal(ctx, "aegis-in-app-challenge");
|
|
2179
2616
|
const tasks = arr(ic.challenge_tasks);
|
|
2180
2617
|
const done = tasks.filter((t) => t.done).length;
|
|
2181
|
-
const pct = tasks.length ? done
|
|
2618
|
+
const pct = tasks.length ? earnPct(done, tasks.length) : 0;
|
|
2182
2619
|
if (str$1(ic.challenge_deadline_text)) s.content.appendChild(gEl("div", "font-size:12px;opacity:0.65;margin-bottom:8px", str$1(ic.challenge_deadline_text)));
|
|
2183
2620
|
tasks.forEach((t) => {
|
|
2184
2621
|
const row = gEl("div", "display:flex;align-items:center;gap:8px;padding:7px 0;border-bottom:1px solid rgba(148,163,184,0.18);font-size:13px;text-align:left");
|
|
@@ -2389,7 +2826,7 @@ function renderStampCard(ctx) {
|
|
|
2389
2826
|
const nextReward = [...rewardAt.keys()].filter((p) => p > filled).sort((a, b) => a - b)[0];
|
|
2390
2827
|
const progressTxt = nextReward ? `${nextReward - filled} more → ${rewardAt.get(nextReward)}` : "Card complete!";
|
|
2391
2828
|
const sub = gEl("div", "font-size:13px;font-weight:600;margin-top:2px");
|
|
2392
|
-
sub.appendChild(fillBar(ink, filled
|
|
2829
|
+
sub.appendChild(fillBar(ink, earnPct(filled, total)));
|
|
2393
2830
|
sub.appendChild(gEl("div", "", `${filled} of ${total}`));
|
|
2394
2831
|
sub.appendChild(gEl("div", "font-weight:400;opacity:0.82;margin-top:1px", str$1(ic.stamp_reward_text) || progressTxt));
|
|
2395
2832
|
s.content.appendChild(sub);
|
|
@@ -2492,7 +2929,7 @@ function renderTierLadder(ctx) {
|
|
|
2492
2929
|
const curIdx = Math.min(list.length - 1, num(ic.tier_current_index, 1));
|
|
2493
2930
|
const curPts = num(ic.tier_current_points, 640);
|
|
2494
2931
|
const nextPts = num(ic.tier_next_points, 1e3);
|
|
2495
|
-
const pct =
|
|
2932
|
+
const pct = earnPct(curPts, nextPts);
|
|
2496
2933
|
const perks = arr(ic.tier_perks);
|
|
2497
2934
|
const next = list[Math.min(list.length - 1, curIdx + 1)];
|
|
2498
2935
|
const ladder = gEl("div", "display:flex;align-items:flex-start;justify-content:center;margin:6px 0 12px");
|
|
@@ -3115,6 +3552,7 @@ class AegisChat {
|
|
|
3115
3552
|
this.title = config.title ?? "Shopping Assistant";
|
|
3116
3553
|
this.greeting = config.greeting ?? "Hi! 👋 How can I help you today?";
|
|
3117
3554
|
this.accent = config.accentColor ?? "#4169e1";
|
|
3555
|
+
this.surfaceStyle = config.surfaceStyle === "tinted" ? "tinted" : "solid";
|
|
3118
3556
|
this.icon = config.icon ?? "sparkle";
|
|
3119
3557
|
this.logoUrl = config.logoUrl;
|
|
3120
3558
|
this.position = config.position ?? "bottom-right";
|
|
@@ -3385,6 +3823,10 @@ class AegisChat {
|
|
|
3385
3823
|
const root = document.createElement("div");
|
|
3386
3824
|
root.className = `aegis-chat-root aegis-chat-root--${this.position}`;
|
|
3387
3825
|
root.style.setProperty("--aegis-chat-accent", this.accent);
|
|
3826
|
+
if (this.surfaceStyle === "tinted") {
|
|
3827
|
+
root.style.setProperty("--aegis-chat-fill", "color-mix(in srgb, var(--aegis-chat-accent,#4169e1) 16%, #ffffff)");
|
|
3828
|
+
root.style.setProperty("--aegis-chat-fill-fg", "#0f172a");
|
|
3829
|
+
}
|
|
3388
3830
|
const bubble = document.createElement("button");
|
|
3389
3831
|
bubble.className = "aegis-chat-bubble";
|
|
3390
3832
|
bubble.setAttribute("aria-label", `Open ${this.title}`);
|
|
@@ -3740,16 +4182,18 @@ const CHAT_CSS = `
|
|
|
3740
4182
|
.aegis-chat-badge{position:absolute;top:-2px;right:-2px;min-width:20px;height:20px;padding:0 5px;border-radius:9999px;background:#ef4444;color:#fff;font-size:12px;font-weight:600;display:flex;align-items:center;justify-content:center}
|
|
3741
4183
|
.aegis-chat-panel{position:absolute;bottom:0;right:0;width:380px;max-width:calc(100vw - 32px);height:560px;max-height:calc(100vh - 48px);background:#fff;border-radius:16px;box-shadow:0 16px 48px rgba(0,0,0,.24);display:none;flex-direction:column;overflow:hidden}
|
|
3742
4184
|
.aegis-chat-panel--open{display:flex}
|
|
3743
|
-
|
|
4185
|
+
/* Header + customer bubbles use the "fill" surface — accent solid by default, or
|
|
4186
|
+
a soft tint when surfaceStyle:'tinted' sets --aegis-chat-fill/-fg on the root. */
|
|
4187
|
+
.aegis-chat-header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;background:var(--aegis-chat-fill,var(--aegis-chat-accent,#4169e1));color:var(--aegis-chat-fill-fg,#fff)}
|
|
3744
4188
|
.aegis-chat-title{font-weight:600;font-size:15px}
|
|
3745
|
-
.aegis-chat-close{background:transparent;border:none;color
|
|
4189
|
+
.aegis-chat-close{background:transparent;border:none;color:var(--aegis-chat-fill-fg,#fff);font-size:16px;cursor:pointer;opacity:.85}
|
|
3746
4190
|
.aegis-chat-close:hover{opacity:1}
|
|
3747
4191
|
.aegis-chat-thread{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;background:#f8fafc}
|
|
3748
4192
|
.aegis-chat-msg{display:flex;flex-direction:column;max-width:85%}
|
|
3749
4193
|
.aegis-chat-msg--customer{align-self:flex-end;align-items:flex-end}
|
|
3750
4194
|
.aegis-chat-msg--assistant,.aegis-chat-msg--system{align-self:flex-start;align-items:flex-start}
|
|
3751
4195
|
.aegis-chat-bubbletext{padding:9px 13px;border-radius:14px;font-size:14px;line-height:1.45;white-space:pre-wrap;word-break:break-word}
|
|
3752
|
-
.aegis-chat-msg--customer .aegis-chat-bubbletext{background:var(--aegis-chat-accent,#4169e1);color
|
|
4196
|
+
.aegis-chat-msg--customer .aegis-chat-bubbletext{background:var(--aegis-chat-fill,var(--aegis-chat-accent,#4169e1));color:var(--aegis-chat-fill-fg,#fff);border-bottom-right-radius:4px}
|
|
3753
4197
|
.aegis-chat-msg--assistant .aegis-chat-bubbletext{background:#fff;color:#0f172a;border:1px solid #e2e8f0;border-bottom-left-radius:4px}
|
|
3754
4198
|
.aegis-chat-quickreplies{display:flex;flex-wrap:wrap;gap:6px;padding:2px 4px 6px;justify-content:flex-start}
|
|
3755
4199
|
.aegis-chat-chip{appearance:none;cursor:pointer;border:1px solid var(--aegis-chat-accent,#4169e1);background:#fff;color:var(--aegis-chat-accent,#4169e1);border-radius:9999px;padding:6px 12px;font-size:13px;line-height:1;font-family:inherit;transition:background .15s,color .15s}
|
|
@@ -4337,6 +4781,13 @@ let activeTriggers = null;
|
|
|
4337
4781
|
function str(v) {
|
|
4338
4782
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
4339
4783
|
}
|
|
4784
|
+
function resolveChatDisplayMode(raw) {
|
|
4785
|
+
var _a, _b;
|
|
4786
|
+
if (raw === "nav") return "nav";
|
|
4787
|
+
if (raw === "bubble") return "bubble";
|
|
4788
|
+
const isMobile = typeof window !== "undefined" && ((_b = (_a = window.matchMedia) == null ? void 0 : _a.call(window, "(max-width: 767px)")) == null ? void 0 : _b.matches) === true;
|
|
4789
|
+
return isMobile ? "nav" : "bubble";
|
|
4790
|
+
}
|
|
4340
4791
|
function renderActiveWebChat(ctx, creds) {
|
|
4341
4792
|
if (typeof document === "undefined") return null;
|
|
4342
4793
|
if (activeInstance) return activeInstance;
|
|
@@ -4355,10 +4806,10 @@ function renderActiveWebChat(ctx, creds) {
|
|
|
4355
4806
|
icon: icon === "sparkle" || icon === "chat" || icon === "logo" ? icon : void 0,
|
|
4356
4807
|
logoUrl: logo ? ctx.sanitizeUrl(logo) ?? void 0 : void 0,
|
|
4357
4808
|
position: ic.chat_position === "bottom-left" ? "bottom-left" : void 0,
|
|
4358
|
-
|
|
4359
|
-
//
|
|
4360
|
-
//
|
|
4361
|
-
displayMode: ic.chat_display_mode
|
|
4809
|
+
surfaceStyle: ic.chat_theme === "tinted" ? "tinted" : void 0,
|
|
4810
|
+
// Explicit chat_display_mode wins; unset → responsive (phones = headless
|
|
4811
|
+
// 'nav' so the host's bottom-nav "Ask" tab is the entry; desktop = 'bubble').
|
|
4812
|
+
displayMode: resolveChatDisplayMode(ic.chat_display_mode),
|
|
4362
4813
|
agentPersona: str(ic.chat_agent_persona),
|
|
4363
4814
|
quickReplies: Array.isArray(ic.chat_quick_replies) ? ic.chat_quick_replies.filter((q) => typeof q === "string") : void 0,
|
|
4364
4815
|
// Bridge the engine's session lifecycle to campaign analytics. Per the
|
|
@@ -4878,6 +5329,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4878
5329
|
this.emitServedEvents(this.campaigns);
|
|
4879
5330
|
this.emit("campaigns-loaded", this.campaigns);
|
|
4880
5331
|
this.readyResolve();
|
|
5332
|
+
this.mountPersistentLaunchers();
|
|
4881
5333
|
this.tryDisplayNextCampaign();
|
|
4882
5334
|
this.renderIntoSlots();
|
|
4883
5335
|
this.renderIntoAnchors();
|
|
@@ -4941,12 +5393,28 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
4941
5393
|
}
|
|
4942
5394
|
tryDisplayNextCampaign() {
|
|
4943
5395
|
const campaign = this.campaigns.find(
|
|
4944
|
-
(c) => !this.displayedCampaigns.has(c.id) && !c.client_trigger && !this.isSuppressed(c.id)
|
|
5396
|
+
(c) => !this.displayedCampaigns.has(c.id) && !c.client_trigger && !this.isSuppressed(c.id) && c.type !== "active_web_chat"
|
|
4945
5397
|
);
|
|
4946
5398
|
if (campaign) {
|
|
4947
5399
|
this.displayCampaign(campaign);
|
|
4948
5400
|
}
|
|
4949
5401
|
}
|
|
5402
|
+
/** Persistent launchers (active_web_chat) mount AMBIENTLY on every arm — a
|
|
5403
|
+
* single always-on entry point, NOT an interruption, so they must not
|
|
5404
|
+
* compete in the one-at-a-time overlay queue (tryDisplayNextCampaign).
|
|
5405
|
+
* Idempotent: renderActiveWebChatLauncher no-ops once a launcher is live and
|
|
5406
|
+
* displayedCampaigns guards re-display. Before this, the launcher only
|
|
5407
|
+
* mounted when it happened to be the single campaign tryDisplayNextCampaign
|
|
5408
|
+
* picked — so aegis.chat.open() / the mobile "Ask" tab silently did nothing
|
|
5409
|
+
* whenever another non-triggered campaign was armed first. */
|
|
5410
|
+
mountPersistentLaunchers() {
|
|
5411
|
+
for (const c of this.campaigns) {
|
|
5412
|
+
if (c.type !== "active_web_chat") continue;
|
|
5413
|
+
if (this.displayedCampaigns.has(c.id)) continue;
|
|
5414
|
+
if (this.isSuppressed(c.id)) continue;
|
|
5415
|
+
this.displayCampaign(c);
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
4950
5418
|
/**
|
|
4951
5419
|
* Scan the page for `[data-aegis-slot]` anchors and render any
|
|
4952
5420
|
* campaign whose `widget_category` matches the slot key AND whose
|
|
@@ -5107,6 +5575,22 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5107
5575
|
this.emit("campaign-shown", c);
|
|
5108
5576
|
};
|
|
5109
5577
|
let idx = 0;
|
|
5578
|
+
const isDarkSurface = (c) => {
|
|
5579
|
+
var _a, _b;
|
|
5580
|
+
const ic = c.interactive_config || {};
|
|
5581
|
+
if (ic.surface_style === "dark") return true;
|
|
5582
|
+
const tplBg = (_b = (_a = ic.card_template) == null ? void 0 : _a.frame) == null ? void 0 : _b.bg;
|
|
5583
|
+
const bg = (typeof tplBg === "string" ? tplBg : "") || c.background_color || "";
|
|
5584
|
+
if (bg.startsWith("#")) {
|
|
5585
|
+
const h = bg.length === 4 ? bg.slice(1).split("").map((x) => x + x).join("") : bg.slice(1, 7);
|
|
5586
|
+
const r = parseInt(h.slice(0, 2), 16), g = parseInt(h.slice(2, 4), 16), b = parseInt(h.slice(4, 6), 16);
|
|
5587
|
+
if (![r, g, b].some(Number.isNaN)) return (0.299 * r + 0.587 * g + 0.114 * b) / 255 < 0.5;
|
|
5588
|
+
}
|
|
5589
|
+
return false;
|
|
5590
|
+
};
|
|
5591
|
+
const darkDots = panes.length > 0 && panes.every((p) => isDarkSurface(p.campaign));
|
|
5592
|
+
const dotInactive = darkDots ? "rgba(255,255,255,0.4)" : "rgba(15,23,42,0.2)";
|
|
5593
|
+
const dotActive = darkDots ? "#ffffff" : "var(--aegis-brand-accent, #4169e1)";
|
|
5110
5594
|
const dots = [];
|
|
5111
5595
|
if (panes.length > 1) {
|
|
5112
5596
|
const bar = document.createElement("div");
|
|
@@ -5119,7 +5603,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5119
5603
|
dot.type = "button";
|
|
5120
5604
|
dot.setAttribute("role", "tab");
|
|
5121
5605
|
dot.setAttribute("aria-label", `Offer ${i + 1} of ${panes.length}`);
|
|
5122
|
-
dot.style.cssText =
|
|
5606
|
+
dot.style.cssText = `border:none;padding:0;cursor:pointer;height:6px;width:6px;border-radius:999px;background:${dotInactive};transition:width .25s ease,background .25s ease;`;
|
|
5123
5607
|
dot.addEventListener("click", (e) => {
|
|
5124
5608
|
e.stopPropagation();
|
|
5125
5609
|
show(i, true);
|
|
@@ -5133,7 +5617,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5133
5617
|
dots.forEach((d, i) => {
|
|
5134
5618
|
const active = i === idx;
|
|
5135
5619
|
d.style.width = active ? "16px" : "6px";
|
|
5136
|
-
d.style.background = active ?
|
|
5620
|
+
d.style.background = active ? dotActive : dotInactive;
|
|
5137
5621
|
d.setAttribute("aria-selected", active ? "true" : "false");
|
|
5138
5622
|
});
|
|
5139
5623
|
};
|
|
@@ -5311,7 +5795,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5311
5795
|
target = sizer;
|
|
5312
5796
|
}
|
|
5313
5797
|
const { bg, text } = this._surfacePalette(campaign);
|
|
5314
|
-
const submitUrl = options == null ? void 0 : options.submitUrl;
|
|
5315
5798
|
const _formFields = Array.isArray(ic.form_fields) ? ic.form_fields : null;
|
|
5316
5799
|
let rendered = false;
|
|
5317
5800
|
if (_formFields && _formFields.length > 0) {
|
|
@@ -5326,43 +5809,22 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5326
5809
|
}
|
|
5327
5810
|
return;
|
|
5328
5811
|
}
|
|
5329
|
-
|
|
5330
|
-
const _hasComposedTemplate = !!_tpl && typeof _tpl === "object" && Array.isArray(_tpl.elements);
|
|
5812
|
+
this._ensureSurveyTemplate(campaign);
|
|
5331
5813
|
const _isMultiCard = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
|
|
5332
|
-
if (_hasComposedTemplate && !_isMultiCard) {
|
|
5814
|
+
if (this._hasComposedTemplate(ic) && !_isMultiCard) {
|
|
5333
5815
|
rendered = this.renderComposedCardSlot(campaign, ic, target);
|
|
5334
5816
|
} else if (campaign.widget_category === "hero") {
|
|
5335
5817
|
rendered = renderHeroInline(this.buildRenderContext(campaign), target);
|
|
5336
5818
|
} else
|
|
5337
5819
|
switch (campaign.sub_type) {
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
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;
|
|
5820
|
+
// P6.full — star_rating / nps_survey / quick_poll slots route through the
|
|
5821
|
+
// unified composed-card slot engine via `_ensureSurveyTemplate` (the gate
|
|
5822
|
+
// above), so the bespoke *Slot survey renderers are RETIRED.
|
|
5358
5823
|
// Parity tracker — built-in embedded gamification renderers (no
|
|
5359
5824
|
// AegisMessageRuntime callback needed, so they work on the bill).
|
|
5360
5825
|
case "spin_wheel":
|
|
5361
5826
|
rendered = this.renderSpinWheelSlot(campaign, ic, bg, text, target);
|
|
5362
5827
|
break;
|
|
5363
|
-
case "quick_poll":
|
|
5364
|
-
rendered = this.renderQuickPollSlot(campaign, ic, bg, text, target);
|
|
5365
|
-
break;
|
|
5366
5828
|
case "carousel_cards":
|
|
5367
5829
|
rendered = this.renderCarouselCardsSlot(campaign, ic, bg, text, target);
|
|
5368
5830
|
break;
|
|
@@ -5407,48 +5869,70 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5407
5869
|
this.emit("campaign-shown", campaign);
|
|
5408
5870
|
}
|
|
5409
5871
|
}
|
|
5410
|
-
/**
|
|
5411
|
-
* Slot-mode wrappers. They build a small card container (no overlay
|
|
5412
|
-
* positioning, no close button) and append the SHARED body element
|
|
5413
|
-
* the overlay path also uses. Body construction lives in the
|
|
5414
|
-
* `_buildXxxBody` helpers below so visual output stays identical
|
|
5415
|
-
* between overlay and slot modes — adding a renderer feature in
|
|
5416
|
-
* one place updates both.
|
|
5417
|
-
*/
|
|
5418
|
-
renderStarRatingSlot(campaign, ic, bg, text, target, submitUrl) {
|
|
5419
|
-
const st = this._slotStyle(campaign);
|
|
5420
|
-
const card = this._wrapInSlotCard(
|
|
5421
|
-
"aegis-in-app-rating-card",
|
|
5422
|
-
campaign.id,
|
|
5423
|
-
bg,
|
|
5424
|
-
text,
|
|
5425
|
-
st.radius
|
|
5426
|
-
);
|
|
5427
|
-
card.appendChild(
|
|
5428
|
-
this._buildStarRatingBody(campaign, ic, text, "slot", submitUrl, st)
|
|
5429
|
-
);
|
|
5430
|
-
target.appendChild(card);
|
|
5431
|
-
return true;
|
|
5432
|
-
}
|
|
5433
|
-
renderNPSSurveySlot(campaign, ic, bg, text, target, submitUrl) {
|
|
5434
|
-
const st = this._slotStyle(campaign);
|
|
5435
|
-
const card = this._wrapInSlotCard(
|
|
5436
|
-
"aegis-in-app-nps-card",
|
|
5437
|
-
campaign.id,
|
|
5438
|
-
bg,
|
|
5439
|
-
text,
|
|
5440
|
-
st.radius
|
|
5441
|
-
);
|
|
5442
|
-
card.appendChild(
|
|
5443
|
-
this._buildNPSSurveyBody(campaign, ic, text, "slot", submitUrl, st)
|
|
5444
|
-
);
|
|
5445
|
-
target.appendChild(card);
|
|
5446
|
-
return true;
|
|
5447
|
-
}
|
|
5448
5872
|
/** §7.13.H — generic embedded card: image / title / body / coupon chip / CTA.
|
|
5449
5873
|
* The catch-all for banner + nudge sub_types (birthday, referral, app-install,
|
|
5450
5874
|
* win-back, etc.) so EVERY gallery nudge renders on the bill (previously only
|
|
5451
5875
|
* star_rating / nps_survey / form were handled → nudges never appeared). */
|
|
5876
|
+
/**
|
|
5877
|
+
* Whether `interactive_config` carries a composed `card_template` (non-empty
|
|
5878
|
+
* `elements[]`). The ONE detector shared by all three composed-card cutover
|
|
5879
|
+
* gates — inline slot, chrome overlay, and the P6 survey-overlay cutover — so
|
|
5880
|
+
* they can't drift (the inline gate previously omitted the non-empty check).
|
|
5881
|
+
*/
|
|
5882
|
+
_hasComposedTemplate(ic) {
|
|
5883
|
+
const tpl = ic.card_template;
|
|
5884
|
+
return !!tpl && typeof tpl === "object" && Array.isArray(tpl.elements) && tpl.elements.length > 0;
|
|
5885
|
+
}
|
|
5886
|
+
/**
|
|
5887
|
+
* P6.full — flat survey → card_template adapter. A `nps_survey` / `star_rating`
|
|
5888
|
+
* / `quick_poll` authored WITHOUT a card_template gets one synthesised in place
|
|
5889
|
+
* (carrying its follow-up config), so the EXISTING composed-template gates route
|
|
5890
|
+
* it through the ONE card-template engine + the shared element builders (with
|
|
5891
|
+
* the ported reason-capture/thank-you flows) — overlay AND slot. Back-compat:
|
|
5892
|
+
* the field names + follow-up defaults match the retired bespoke renderers.
|
|
5893
|
+
* No-op when a template already exists or the type isn't a single-card survey.
|
|
5894
|
+
*/
|
|
5895
|
+
_ensureSurveyTemplate(campaign) {
|
|
5896
|
+
const ic = campaign.interactive_config || {};
|
|
5897
|
+
if (this._hasComposedTemplate(ic)) return;
|
|
5898
|
+
let element = null;
|
|
5899
|
+
if (campaign.sub_type === "nps_survey") {
|
|
5900
|
+
element = {
|
|
5901
|
+
type: "nps",
|
|
5902
|
+
content: ic.question,
|
|
5903
|
+
thanks_text: ic.thank_you_message,
|
|
5904
|
+
nps_followup_enabled: ic.nps_followup_enabled !== false,
|
|
5905
|
+
nps_follow_up_promoter: ic.nps_follow_up_promoter,
|
|
5906
|
+
nps_follow_up_passive: ic.nps_follow_up_passive,
|
|
5907
|
+
nps_follow_up_detractor: ic.nps_follow_up_detractor,
|
|
5908
|
+
nps_reason_chips: ic.nps_reason_chips,
|
|
5909
|
+
nps_promoter_threshold: ic.nps_promoter_threshold,
|
|
5910
|
+
nps_promoter_url: ic.nps_promoter_url,
|
|
5911
|
+
nps_promoter_cta: ic.nps_promoter_cta
|
|
5912
|
+
};
|
|
5913
|
+
} else if (campaign.sub_type === "star_rating") {
|
|
5914
|
+
element = {
|
|
5915
|
+
type: "rating_input",
|
|
5916
|
+
content: ic.question,
|
|
5917
|
+
rating_scale: ic.rating_scale,
|
|
5918
|
+
thanks_text: ic.thank_you_message,
|
|
5919
|
+
rating_followup_enabled: ic.rating_followup_enabled !== false,
|
|
5920
|
+
rating_followup_threshold: ic.rating_followup_threshold,
|
|
5921
|
+
rating_followup_prompt: ic.rating_followup_prompt,
|
|
5922
|
+
rating_high_url: ic.rating_high_url ?? ic.nps_promoter_url,
|
|
5923
|
+
rating_high_cta: ic.rating_high_cta
|
|
5924
|
+
};
|
|
5925
|
+
} else if (campaign.sub_type === "quick_poll") {
|
|
5926
|
+
const opts = Array.isArray(ic.poll_options) ? ic.poll_options : Array.isArray(ic.options) ? ic.options : [];
|
|
5927
|
+
if (opts.length >= 2) {
|
|
5928
|
+
element = { type: "poll", content: ic.question, poll_options: opts, thanks_text: ic.thank_you_message };
|
|
5929
|
+
}
|
|
5930
|
+
}
|
|
5931
|
+
if (!element) return;
|
|
5932
|
+
const cleaned = Object.fromEntries(Object.entries(element).filter(([, v]) => v !== void 0));
|
|
5933
|
+
ic.card_template = { frame: {}, elements: [cleaned] };
|
|
5934
|
+
campaign.interactive_config = ic;
|
|
5935
|
+
}
|
|
5452
5936
|
/**
|
|
5453
5937
|
* Composed single card — renders a campaign's top-level `card_template`
|
|
5454
5938
|
* (frame + elements: icon/text/progress/image/cta/group/…) through the generic
|
|
@@ -5477,6 +5961,57 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5477
5961
|
target.appendChild(tile);
|
|
5478
5962
|
return true;
|
|
5479
5963
|
}
|
|
5964
|
+
/**
|
|
5965
|
+
* C2 — composed card body in an OVERLAY (modal / banner / full_screen /
|
|
5966
|
+
* half_interstitial). The overlay counterpart to `renderComposedCardSlot`: it
|
|
5967
|
+
* builds the same `TemplateCard` from the campaign's canonical fields and paints
|
|
5968
|
+
* it with the generic `renderCardFromTemplate` engine, then wraps it in the
|
|
5969
|
+
* standard overlay chrome (close button + size scaling) so a composed card looks
|
|
5970
|
+
* identical whether overlaid or slotted. Only invoked when a `card_template` is
|
|
5971
|
+
* present (see displayCampaign) — flat campaigns never reach here.
|
|
5972
|
+
* See IN_APP_FORMAT_LOCK_AND_COMPOSABLE_CARDS_TRACKER.md (Part B / C2).
|
|
5973
|
+
*/
|
|
5974
|
+
renderComposedCardOverlay(campaign, ic) {
|
|
5975
|
+
const { accent } = this._surfacePalette(campaign);
|
|
5976
|
+
const bg = this.sanitizeColor(campaign.background_color || "#ffffff");
|
|
5977
|
+
const text = this.sanitizeColor(campaign.text_color || "#111827");
|
|
5978
|
+
const fullScreen = campaign.type === "full_screen";
|
|
5979
|
+
const overlay = this.createOverlay("aegis-in-app-composed-card-overlay");
|
|
5980
|
+
const modal = document.createElement("div");
|
|
5981
|
+
modal.style.cssText = fullScreen ? `position: relative; width: 100%; height: 100%; max-width: 100%; overflow: auto;
|
|
5982
|
+
background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;` : `position: relative; max-width: 380px; width: 92%; border-radius: 18px; overflow: hidden;
|
|
5983
|
+
background: ${bg}; color: ${text}; box-shadow: 0 16px 48px rgba(0,0,0,0.22);
|
|
5984
|
+
animation: aegisScaleIn 0.3s ease;`;
|
|
5985
|
+
const c = campaign;
|
|
5986
|
+
const card = {
|
|
5987
|
+
image_url: typeof c.image_url === "string" ? c.image_url : void 0,
|
|
5988
|
+
title: campaign.title,
|
|
5989
|
+
body: campaign.body,
|
|
5990
|
+
cta_text: typeof c.button_text === "string" ? c.button_text : void 0,
|
|
5991
|
+
cta_url: typeof c.action_url === "string" ? c.action_url : void 0,
|
|
5992
|
+
metadata: ic.card_meta || {}
|
|
5993
|
+
};
|
|
5994
|
+
const template = resolveGlobalTemplate(ic);
|
|
5995
|
+
const tile = renderCardFromTemplate(card, {
|
|
5996
|
+
template,
|
|
5997
|
+
ctx: this.buildRenderContext(campaign),
|
|
5998
|
+
action: "navigate",
|
|
5999
|
+
layoutAccent: this.sanitizeColor(campaign.background_color || accent)
|
|
6000
|
+
});
|
|
6001
|
+
modal.appendChild(tile);
|
|
6002
|
+
this._addCornerClose(modal, overlay, campaign.id, text);
|
|
6003
|
+
if (fullScreen) {
|
|
6004
|
+
overlay.appendChild(modal);
|
|
6005
|
+
} else {
|
|
6006
|
+
const mul = this._sizeMul(ic);
|
|
6007
|
+
const sizer = document.createElement("div");
|
|
6008
|
+
sizer.style.cssText = `display: flex; justify-content: center; align-items: center; width: 100%; transform: scale(${mul}); transform-origin: center;`;
|
|
6009
|
+
sizer.appendChild(modal);
|
|
6010
|
+
overlay.appendChild(sizer);
|
|
6011
|
+
}
|
|
6012
|
+
this.addAnimationStyles();
|
|
6013
|
+
document.body.appendChild(overlay);
|
|
6014
|
+
}
|
|
5480
6015
|
renderGenericCardSlot(campaign, ic, bg, text, target) {
|
|
5481
6016
|
const st = this._slotStyle(campaign);
|
|
5482
6017
|
const card = this._wrapInSlotCard("aegis-in-app-generic-card", campaign.id, bg, text, st.radius);
|
|
@@ -6350,88 +6885,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
6350
6885
|
}
|
|
6351
6886
|
return true;
|
|
6352
6887
|
}
|
|
6353
|
-
/** Built-in embedded QUICK POLL — option buttons submit the chosen index. */
|
|
6354
|
-
renderQuickPollSlot(campaign, ic, bg, text, target) {
|
|
6355
|
-
const st = this._slotStyle(campaign);
|
|
6356
|
-
const card = this._wrapInSlotCard("aegis-in-app-poll-card", campaign.id, bg, text, st.radius);
|
|
6357
|
-
const opts = Array.isArray(ic.poll_options) ? ic.poll_options.filter((o) => typeof o === "string") : [];
|
|
6358
|
-
const body = document.createElement("div");
|
|
6359
|
-
body.style.cssText = `padding: ${st.padY ?? 12}px ${st.padX ?? 12}px; display: flex; flex-direction: column; gap: 6px;`;
|
|
6360
|
-
if (campaign.title) {
|
|
6361
|
-
const t = document.createElement("div");
|
|
6362
|
-
t.style.cssText = `font-size: 13px; font-weight: ${st.weight ?? "700"};`;
|
|
6363
|
-
t.textContent = campaign.title;
|
|
6364
|
-
body.appendChild(t);
|
|
6365
|
-
}
|
|
6366
|
-
const buttons = [];
|
|
6367
|
-
opts.forEach((opt, i) => {
|
|
6368
|
-
const ob = document.createElement("button");
|
|
6369
|
-
ob.style.cssText = `position: relative; overflow: hidden; text-align: left; padding: 8px 10px; border-radius: ${st.btnRadius ?? 8}px; border: 1px solid ${text}40; background: transparent; color: ${text}; font-size: 12px; cursor: pointer; transition: border-color 0.2s ease;`;
|
|
6370
|
-
const lbl = document.createElement("span");
|
|
6371
|
-
lbl.style.cssText = "position: relative; z-index: 1; display: flex; justify-content: space-between; gap: 8px;";
|
|
6372
|
-
const lblText = document.createElement("span");
|
|
6373
|
-
lblText.textContent = opt;
|
|
6374
|
-
const lblPct = document.createElement("span");
|
|
6375
|
-
lblPct.style.cssText = "font-weight: 700; opacity: 0; transition: opacity 0.3s ease;";
|
|
6376
|
-
lbl.appendChild(lblText);
|
|
6377
|
-
lbl.appendChild(lblPct);
|
|
6378
|
-
ob.appendChild(lbl);
|
|
6379
|
-
ob.addEventListener("click", () => {
|
|
6380
|
-
void this.trackEvent(campaign.id, "clicked");
|
|
6381
|
-
buttons.forEach((b) => b.disabled = true);
|
|
6382
|
-
this._paintPollResult(buttons, opts, i, null, text);
|
|
6383
|
-
this.addAnimationStyles();
|
|
6384
|
-
void this.submitWidgetResponse(campaign, "poll", { option_index: i, option_label: opt }).then((resp) => {
|
|
6385
|
-
const dist = this._readPollDistribution(resp, opts.length);
|
|
6386
|
-
if (dist) this._paintPollResult(buttons, opts, i, dist, text);
|
|
6387
|
-
});
|
|
6388
|
-
this._playReaction(card, "affirm", "check.json", ic);
|
|
6389
|
-
});
|
|
6390
|
-
buttons.push(ob);
|
|
6391
|
-
body.appendChild(ob);
|
|
6392
|
-
});
|
|
6393
|
-
card.appendChild(body);
|
|
6394
|
-
target.appendChild(card);
|
|
6395
|
-
return true;
|
|
6396
|
-
}
|
|
6397
|
-
/** Pull a poll vote distribution out of the server response, if present.
|
|
6398
|
-
* Accepts `results`/`distribution`/`tallies` as a number[] aligned to the
|
|
6399
|
-
* option order. Returns null when the server didn't surface tallies (then
|
|
6400
|
-
* we only confirm the chosen option — never fabricate percentages). */
|
|
6401
|
-
_readPollDistribution(resp, count) {
|
|
6402
|
-
if (!resp) return null;
|
|
6403
|
-
const raw = resp.results ?? resp.distribution ?? resp.tallies;
|
|
6404
|
-
if (!Array.isArray(raw) || raw.length !== count) return null;
|
|
6405
|
-
const nums = raw.map((v) => typeof v === "number" && isFinite(v) && v >= 0 ? v : 0);
|
|
6406
|
-
return nums.some((n) => n > 0) ? nums : null;
|
|
6407
|
-
}
|
|
6408
|
-
/** Animate result bars on the poll options. With a real `dist` it renders
|
|
6409
|
-
* each option's share; without one it just fills the selected option 100%
|
|
6410
|
-
* as a "vote locked in" confirmation. */
|
|
6411
|
-
_paintPollResult(buttons, _opts, selected, dist, text) {
|
|
6412
|
-
const total = dist ? dist.reduce((a, b) => a + b, 0) || 1 : 1;
|
|
6413
|
-
buttons.forEach((ob, i) => {
|
|
6414
|
-
const pct = dist ? Math.round(dist[i] / total * 100) : i === selected ? 100 : 0;
|
|
6415
|
-
let fill = ob.querySelector(".aegis-poll-fill");
|
|
6416
|
-
if (!fill) {
|
|
6417
|
-
fill = document.createElement("span");
|
|
6418
|
-
fill.className = "aegis-poll-fill";
|
|
6419
|
-
fill.style.cssText = `position: absolute; inset: 0 auto 0 0; z-index: 0; background: ${i === selected ? text : text + "22"}; opacity: ${i === selected ? "0.16" : "0.1"};`;
|
|
6420
|
-
ob.insertBefore(fill, ob.firstChild);
|
|
6421
|
-
}
|
|
6422
|
-
fill.style.setProperty("--pct", `${pct}%`);
|
|
6423
|
-
fill.style.animation = "aegisPollFill 0.5s ease forwards";
|
|
6424
|
-
if (i === selected) ob.style.borderColor = text;
|
|
6425
|
-
const pctLabel = ob.querySelector("span > span:last-child");
|
|
6426
|
-
if (pctLabel && dist) {
|
|
6427
|
-
pctLabel.textContent = `${pct}%`;
|
|
6428
|
-
pctLabel.style.opacity = "0.8";
|
|
6429
|
-
} else if (pctLabel && i === selected) {
|
|
6430
|
-
pctLabel.innerHTML = lucideSvg("check", { size: 14 });
|
|
6431
|
-
pctLabel.style.opacity = "0.8";
|
|
6432
|
-
}
|
|
6433
|
-
});
|
|
6434
|
-
}
|
|
6435
6888
|
/**
|
|
6436
6889
|
* Built-in embedded IMAGE/VIDEO CAROUSEL — a swipeable horizontal strip of
|
|
6437
6890
|
* `interactive_config.cards` (image OR video + title + body + CTA), contained
|
|
@@ -7010,23 +7463,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7010
7463
|
for (const c of cands) if (typeof c === "string" && c) return c;
|
|
7011
7464
|
return null;
|
|
7012
7465
|
}
|
|
7013
|
-
/**
|
|
7014
|
-
* Token-bound submission helper. POSTs the structured response to
|
|
7015
|
-
* the per-token submit URL the page handler embedded. Best-effort
|
|
7016
|
-
* — failure is logged but doesn't throw (the customer already
|
|
7017
|
-
* clicked; we can't undo their interaction).
|
|
7018
|
-
*/
|
|
7019
|
-
_submitTokenResponse(submitUrl, payload) {
|
|
7020
|
-
if (!submitUrl) return;
|
|
7021
|
-
fetch(submitUrl, {
|
|
7022
|
-
method: "POST",
|
|
7023
|
-
headers: { "Content-Type": "application/json" },
|
|
7024
|
-
body: JSON.stringify({ response: payload }),
|
|
7025
|
-
keepalive: true
|
|
7026
|
-
}).catch((err) => {
|
|
7027
|
-
this.log(`token submit failed: ${err}`, "warn");
|
|
7028
|
-
});
|
|
7029
|
-
}
|
|
7030
7466
|
// --- Shared body builders (used by both overlay + slot paths) ----------
|
|
7031
7467
|
//
|
|
7032
7468
|
// Sizing: `'overlay'` matches the legacy fullscreen-modal scale (24px
|
|
@@ -7087,398 +7523,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7087
7523
|
el.style.boxShadow = "none";
|
|
7088
7524
|
}
|
|
7089
7525
|
}
|
|
7090
|
-
// Optional header/brand image atop a survey-style widget (star / nps).
|
|
7091
|
-
_appendHeaderImage(body, ic) {
|
|
7092
|
-
const url = ic.header_image_url;
|
|
7093
|
-
if (!url) return;
|
|
7094
|
-
const safe = this.sanitizeUrl(url);
|
|
7095
|
-
if (!safe) return;
|
|
7096
|
-
const img = document.createElement("img");
|
|
7097
|
-
img.src = safe;
|
|
7098
|
-
img.alt = "";
|
|
7099
|
-
img.style.cssText = "display: block; max-height: 72px; max-width: 60%; margin: 0 auto 14px; object-fit: contain;";
|
|
7100
|
-
body.appendChild(img);
|
|
7101
|
-
}
|
|
7102
|
-
_buildStarRatingBody(campaign, ic, text, variant, submitUrl, slotStyle) {
|
|
7103
|
-
const isOverlay = variant === "overlay";
|
|
7104
|
-
const padding = isOverlay ? "24px" : `${(slotStyle == null ? void 0 : slotStyle.padY) ?? 20}px ${(slotStyle == null ? void 0 : slotStyle.padX) ?? 20}px`;
|
|
7105
|
-
const titleSize = isOverlay ? "18px" : "16px";
|
|
7106
|
-
const titleWeight = isOverlay ? "700" : (slotStyle == null ? void 0 : slotStyle.weight) ?? "600";
|
|
7107
|
-
const titleMargin = isOverlay ? "16px" : "12px";
|
|
7108
|
-
const starSize = isOverlay ? "32px" : "28px";
|
|
7109
|
-
const starsMarginBottom = isOverlay ? "16px" : "0";
|
|
7110
|
-
const hoverScale = isOverlay ? "1.2" : "1.15";
|
|
7111
|
-
const body = document.createElement("div");
|
|
7112
|
-
body.style.cssText = `padding: ${padding}; text-align: center;`;
|
|
7113
|
-
this._appendHeaderImage(body, ic);
|
|
7114
|
-
const title = document.createElement("div");
|
|
7115
|
-
title.style.cssText = `font-size: ${titleSize}; font-weight: ${titleWeight}; margin-bottom: ${titleMargin};`;
|
|
7116
|
-
title.textContent = campaign.title || "Rate your experience";
|
|
7117
|
-
body.appendChild(title);
|
|
7118
|
-
const stars = document.createElement("div");
|
|
7119
|
-
stars.style.cssText = `display: flex; gap: 8px; justify-content: center;` + (starsMarginBottom !== "0" ? ` margin-bottom: ${starsMarginBottom};` : "");
|
|
7120
|
-
const maxStars = ic.rating_scale || 5;
|
|
7121
|
-
const GOLD = "#fbbf24";
|
|
7122
|
-
const iconSet = ic.rating_icon || "star";
|
|
7123
|
-
const EMOJI = ["😞", "🙁", "😐", "🙂", "😀", "🤩"];
|
|
7124
|
-
const emojiFor = (idx) => {
|
|
7125
|
-
const k = maxStars <= 1 ? EMOJI.length - 1 : Math.round(idx / (maxStars - 1) * (EMOJI.length - 1));
|
|
7126
|
-
return EMOJI[Math.max(0, Math.min(EMOJI.length - 1, k))];
|
|
7127
|
-
};
|
|
7128
|
-
const glyph = (on, idx) => iconSet === "emoji" ? emojiFor(idx) : iconSet === "heart" ? on ? "♥" : "♡" : on ? "★" : "☆";
|
|
7129
|
-
const labels = Array.isArray(ic.rating_labels) ? ic.rating_labels : null;
|
|
7130
|
-
let caption2 = null;
|
|
7131
|
-
if (labels && labels.length > 0) {
|
|
7132
|
-
caption2 = document.createElement("div");
|
|
7133
|
-
caption2.style.cssText = `font-size: 12px; opacity: 0.7; margin-top: 8px; min-height: 16px; color: ${text || "inherit"};`;
|
|
7134
|
-
}
|
|
7135
|
-
const starEls = [];
|
|
7136
|
-
let selected = 0;
|
|
7137
|
-
const paint = (n) => {
|
|
7138
|
-
starEls.forEach((el, idx) => {
|
|
7139
|
-
const on = idx < n;
|
|
7140
|
-
el.textContent = glyph(on, idx);
|
|
7141
|
-
if (iconSet === "emoji") {
|
|
7142
|
-
el.style.opacity = on || n === 0 ? "1" : "0.3";
|
|
7143
|
-
el.style.color = "inherit";
|
|
7144
|
-
} else {
|
|
7145
|
-
el.style.color = on ? GOLD : text || "inherit";
|
|
7146
|
-
}
|
|
7147
|
-
el.style.transform = on && idx === n - 1 ? `scale(${hoverScale})` : "scale(1)";
|
|
7148
|
-
});
|
|
7149
|
-
if (caption2 && labels) {
|
|
7150
|
-
const lbl = n > 0 ? labels[Math.min(n - 1, labels.length - 1)] : "";
|
|
7151
|
-
caption2.textContent = lbl || "";
|
|
7152
|
-
}
|
|
7153
|
-
};
|
|
7154
|
-
for (let i = 1; i <= maxStars; i++) {
|
|
7155
|
-
const star = document.createElement("span");
|
|
7156
|
-
star.style.cssText = `font-size: ${starSize}; cursor: pointer; transition: transform 0.12s, color 0.12s, opacity 0.12s; user-select: none;` + (iconSet !== "emoji" && text ? ` color: ${text};` : "") + (iconSet === "emoji" ? " opacity: 0.3;" : "");
|
|
7157
|
-
star.setAttribute("role", "button");
|
|
7158
|
-
star.setAttribute("aria-label", `${i} ${iconSet === "heart" ? "heart" : "star"}${i > 1 ? "s" : ""}`);
|
|
7159
|
-
star.textContent = glyph(false, i - 1);
|
|
7160
|
-
const value = i;
|
|
7161
|
-
star.addEventListener("mouseenter", () => paint(value));
|
|
7162
|
-
star.addEventListener("mouseleave", () => paint(selected));
|
|
7163
|
-
star.addEventListener("click", () => {
|
|
7164
|
-
selected = value;
|
|
7165
|
-
paint(selected);
|
|
7166
|
-
star.style.transform = "scale(1.28)";
|
|
7167
|
-
setTimeout(() => paint(selected), 160);
|
|
7168
|
-
this.trackEvent(campaign.id, "clicked");
|
|
7169
|
-
this._onStarRatingPicked(campaign, ic, body, value, maxStars, text, isOverlay, submitUrl);
|
|
7170
|
-
});
|
|
7171
|
-
starEls.push(star);
|
|
7172
|
-
stars.appendChild(star);
|
|
7173
|
-
}
|
|
7174
|
-
body.appendChild(stars);
|
|
7175
|
-
if (caption2) body.appendChild(caption2);
|
|
7176
|
-
return body;
|
|
7177
|
-
}
|
|
7178
|
-
/**
|
|
7179
|
-
* Post-rating flow — the Reelo/Plotline "smart rating" gate. A happy rater
|
|
7180
|
-
* (>= threshold) is invited to leave a PUBLIC review; an unhappy one is given
|
|
7181
|
-
* a PRIVATE "what went wrong?" comment box so the complaint is captured for
|
|
7182
|
-
* the operator instead of landing on a public platform. Exactly ONE response
|
|
7183
|
-
* row is written per rating (deferred on the low path until Send/Skip) so the
|
|
7184
|
-
* star distribution isn't double-counted.
|
|
7185
|
-
*/
|
|
7186
|
-
_onStarRatingPicked(campaign, ic, body, value, maxStars, text, isOverlay, submitUrl) {
|
|
7187
|
-
if (submitUrl) {
|
|
7188
|
-
this._submitTokenResponse(submitUrl, { sub_type: "star_rating", value });
|
|
7189
|
-
}
|
|
7190
|
-
const followupEnabled = isOverlay && ic.rating_followup_enabled !== false;
|
|
7191
|
-
const threshold = ic.rating_followup_threshold || Math.ceil(maxStars * 0.8);
|
|
7192
|
-
const isHigh = value >= threshold;
|
|
7193
|
-
if (!isOverlay) {
|
|
7194
|
-
if (isHigh) this._playReaction(body, "affirm", void 0, ic);
|
|
7195
|
-
else if (value <= Math.max(1, Math.floor(maxStars * 0.4))) this._playReaction(body, "empathize", void 0, ic);
|
|
7196
|
-
else this._playReaction(body, "acknowledge", void 0, ic);
|
|
7197
|
-
return;
|
|
7198
|
-
}
|
|
7199
|
-
if (!followupEnabled) {
|
|
7200
|
-
void this.submitWidgetResponse(campaign, "rating", { stars: value });
|
|
7201
|
-
this._renderRatingThankYou(campaign, ic, body, isHigh);
|
|
7202
|
-
return;
|
|
7203
|
-
}
|
|
7204
|
-
if (isHigh) {
|
|
7205
|
-
void this.submitWidgetResponse(campaign, "rating", { stars: value });
|
|
7206
|
-
this._renderRatingThankYou(campaign, ic, body, true);
|
|
7207
|
-
} else {
|
|
7208
|
-
this._renderRatingLowFollowup(campaign, ic, body, value, text);
|
|
7209
|
-
}
|
|
7210
|
-
}
|
|
7211
|
-
/** Thank-you takeover; happy raters also get a public-review CTA when authored. */
|
|
7212
|
-
_renderRatingThankYou(campaign, ic, body, isHigh) {
|
|
7213
|
-
while (body.firstChild) body.removeChild(body.firstChild);
|
|
7214
|
-
const modalEl = body.parentElement;
|
|
7215
|
-
if (modalEl) modalEl.style.overflow = "visible";
|
|
7216
|
-
body.style.position = "relative";
|
|
7217
|
-
body.style.minHeight = "176px";
|
|
7218
|
-
body.style.display = "flex";
|
|
7219
|
-
body.style.flexDirection = "column";
|
|
7220
|
-
body.style.alignItems = "center";
|
|
7221
|
-
body.style.justifyContent = "center";
|
|
7222
|
-
const heading = document.createElement("div");
|
|
7223
|
-
heading.style.cssText = "font-size: 16px; font-weight: 700; margin-bottom: 8px;";
|
|
7224
|
-
heading.textContent = isHigh ? ic.rating_high_prompt || "Thanks — glad you loved it!" : ic.thank_you_message || "Thanks for your feedback!";
|
|
7225
|
-
body.appendChild(heading);
|
|
7226
|
-
const reviewUrl = ic.rating_review_url;
|
|
7227
|
-
if (isHigh && reviewUrl) {
|
|
7228
|
-
const sub = document.createElement("div");
|
|
7229
|
-
sub.style.cssText = "font-size: 13px; opacity: 0.8; margin-bottom: 14px;";
|
|
7230
|
-
sub.textContent = "Would you share that with others?";
|
|
7231
|
-
body.appendChild(sub);
|
|
7232
|
-
const st = this.inAppStyle(campaign);
|
|
7233
|
-
const cta = document.createElement("button");
|
|
7234
|
-
cta.type = "button";
|
|
7235
|
-
cta.textContent = campaign.button_text || "Leave a review";
|
|
7236
|
-
cta.style.cssText = `padding: 10px 24px; border-radius: 999px; border: none; background: ${st.btnBg}; color: ${st.btnText}; font-weight: 800; font-size: 13.5px; cursor: pointer;`;
|
|
7237
|
-
cta.addEventListener("click", () => {
|
|
7238
|
-
this.trackEvent(campaign.id, "clicked", { meta: { action: "review_redirect" } });
|
|
7239
|
-
const safe = this.sanitizeUrl(reviewUrl);
|
|
7240
|
-
if (safe) this.navigateToCampaignAction(campaign, safe);
|
|
7241
|
-
});
|
|
7242
|
-
body.appendChild(cta);
|
|
7243
|
-
} else {
|
|
7244
|
-
this._playReaction(body, isHigh ? "affirm" : "acknowledge", void 0, ic);
|
|
7245
|
-
}
|
|
7246
|
-
}
|
|
7247
|
-
/** Unhappy-path private comment capture; submits {stars, comment} once. */
|
|
7248
|
-
_renderRatingLowFollowup(campaign, ic, body, value, text) {
|
|
7249
|
-
this.addAnimationStyles();
|
|
7250
|
-
while (body.firstChild) body.removeChild(body.firstChild);
|
|
7251
|
-
const heading = document.createElement("div");
|
|
7252
|
-
heading.style.cssText = "font-size: 15px; font-weight: 700; margin-bottom: 10px;";
|
|
7253
|
-
heading.textContent = ic.rating_low_prompt || "Sorry to hear that. What went wrong?";
|
|
7254
|
-
body.appendChild(heading);
|
|
7255
|
-
const ta = document.createElement("textarea");
|
|
7256
|
-
ta.className = "aegis-rating-comment";
|
|
7257
|
-
ta.rows = 3;
|
|
7258
|
-
ta.placeholder = "Tell us how we can do better…";
|
|
7259
|
-
ta.style.cssText = `width: 100%; box-sizing: border-box; padding: 10px; border-radius: 10px; border: 1px solid ${text}33; background: transparent; color: ${text || "inherit"}; font-size: 13px; resize: vertical; margin-bottom: 12px; font-family: inherit;`;
|
|
7260
|
-
body.appendChild(ta);
|
|
7261
|
-
const st = this.inAppStyle(campaign);
|
|
7262
|
-
const row = document.createElement("div");
|
|
7263
|
-
row.style.cssText = "display: flex; gap: 8px; justify-content: center;";
|
|
7264
|
-
let done = false;
|
|
7265
|
-
const finish = (withComment) => {
|
|
7266
|
-
if (done) return;
|
|
7267
|
-
done = true;
|
|
7268
|
-
const comment = withComment ? ta.value.trim() : "";
|
|
7269
|
-
void this.submitWidgetResponse(campaign, "rating", comment ? { stars: value, comment } : { stars: value });
|
|
7270
|
-
this._renderRatingThankYou(campaign, ic, body, false);
|
|
7271
|
-
};
|
|
7272
|
-
const send = document.createElement("button");
|
|
7273
|
-
send.type = "button";
|
|
7274
|
-
send.textContent = "Send feedback";
|
|
7275
|
-
send.style.cssText = `padding: 9px 20px; border-radius: 999px; border: none; background: ${st.btnBg}; color: ${st.btnText}; font-weight: 800; font-size: 13px; cursor: pointer;`;
|
|
7276
|
-
send.addEventListener("click", () => finish(true));
|
|
7277
|
-
const skip = document.createElement("button");
|
|
7278
|
-
skip.type = "button";
|
|
7279
|
-
skip.textContent = "Skip";
|
|
7280
|
-
skip.style.cssText = `padding: 9px 16px; border-radius: 999px; border: 1px solid ${text}33; background: transparent; color: ${text || "inherit"}; font-weight: 600; font-size: 13px; cursor: pointer;`;
|
|
7281
|
-
skip.addEventListener("click", () => finish(false));
|
|
7282
|
-
row.appendChild(send);
|
|
7283
|
-
row.appendChild(skip);
|
|
7284
|
-
body.appendChild(row);
|
|
7285
|
-
ta.focus();
|
|
7286
|
-
}
|
|
7287
|
-
_buildNPSSurveyBody(campaign, ic, text, variant, submitUrl, slotStyle) {
|
|
7288
|
-
const isOverlay = variant === "overlay";
|
|
7289
|
-
const padding = isOverlay ? "24px" : `${(slotStyle == null ? void 0 : slotStyle.padY) ?? 20}px ${(slotStyle == null ? void 0 : slotStyle.padX) ?? 20}px`;
|
|
7290
|
-
const titleAlign = isOverlay ? "center" : "center";
|
|
7291
|
-
const body = document.createElement("div");
|
|
7292
|
-
body.style.cssText = `padding: ${padding};` + (isOverlay ? " text-align: center;" : "");
|
|
7293
|
-
this._appendHeaderImage(body, ic);
|
|
7294
|
-
const title = document.createElement("div");
|
|
7295
|
-
title.style.cssText = `font-size: 16px; font-weight: ${isOverlay ? "700" : (slotStyle == null ? void 0 : slotStyle.weight) ?? "600"}; margin-bottom: ${isOverlay ? "16px" : "12px"}; text-align: ${titleAlign};`;
|
|
7296
|
-
title.textContent = ic.nps_question || campaign.title || "How likely are you to recommend us?";
|
|
7297
|
-
body.appendChild(title);
|
|
7298
|
-
const npsColor = (n) => n <= 6 ? "#ef4444" : n <= 8 ? "#f59e0b" : "#10b981";
|
|
7299
|
-
const scale = document.createElement("div");
|
|
7300
|
-
scale.style.cssText = isOverlay ? "display: flex; gap: 4px; justify-content: center; flex-wrap: wrap; margin-bottom: 12px;" : "display: grid; grid-template-columns: repeat(11, 1fr); gap: 4px;";
|
|
7301
|
-
const onPick = (value) => {
|
|
7302
|
-
this.trackEvent(campaign.id, "clicked");
|
|
7303
|
-
if (submitUrl) {
|
|
7304
|
-
this._submitTokenResponse(submitUrl, { sub_type: "nps_survey", value });
|
|
7305
|
-
}
|
|
7306
|
-
const followupEnabled = isOverlay && ic.nps_followup_enabled !== false;
|
|
7307
|
-
if (followupEnabled) {
|
|
7308
|
-
this._renderNpsReasonFollowup(campaign, ic, body, value, text);
|
|
7309
|
-
return;
|
|
7310
|
-
}
|
|
7311
|
-
void this.submitWidgetResponse(campaign, "nps", { score: value });
|
|
7312
|
-
this.addAnimationStyles();
|
|
7313
|
-
const thanks = document.createElement("div");
|
|
7314
|
-
thanks.style.cssText = `font-size: 14px; font-weight: 600; padding: 8px 0; text-align: center; color: ${text};`;
|
|
7315
|
-
thanks.textContent = value >= 9 ? "Thanks! We are glad you love it." : value <= 6 ? "Thanks for the honest feedback — we will do better." : "Thank you for your feedback!";
|
|
7316
|
-
scale.replaceWith(thanks);
|
|
7317
|
-
labels == null ? void 0 : labels.remove();
|
|
7318
|
-
if (value >= 9) this._playReaction(body, "affirm", void 0, ic);
|
|
7319
|
-
else if (value <= 6) this._playReaction(body, "empathize", void 0, ic);
|
|
7320
|
-
else this._playReaction(body, "acknowledge", void 0, ic);
|
|
7321
|
-
};
|
|
7322
|
-
for (let n = 0; n <= 10; n++) {
|
|
7323
|
-
const col = npsColor(n);
|
|
7324
|
-
const value = n;
|
|
7325
|
-
if (isOverlay) {
|
|
7326
|
-
const btn = document.createElement("span");
|
|
7327
|
-
btn.style.cssText = `
|
|
7328
|
-
width: 28px; height: 28px; border-radius: 6px; display: flex;
|
|
7329
|
-
align-items: center; justify-content: center; font-size: 12px;
|
|
7330
|
-
font-weight: 700; cursor: pointer; background: ${col}; color: #fff;
|
|
7331
|
-
opacity: 0.85; transition: transform 0.1s, opacity 0.15s;
|
|
7332
|
-
`;
|
|
7333
|
-
btn.textContent = String(n);
|
|
7334
|
-
btn.addEventListener("mouseenter", () => {
|
|
7335
|
-
btn.style.opacity = "1";
|
|
7336
|
-
btn.style.transform = "translateY(-1px)";
|
|
7337
|
-
});
|
|
7338
|
-
btn.addEventListener("mouseleave", () => {
|
|
7339
|
-
btn.style.opacity = "0.85";
|
|
7340
|
-
btn.style.transform = "translateY(0)";
|
|
7341
|
-
});
|
|
7342
|
-
btn.addEventListener("click", () => onPick(value));
|
|
7343
|
-
scale.appendChild(btn);
|
|
7344
|
-
} else {
|
|
7345
|
-
const btn = document.createElement("button");
|
|
7346
|
-
btn.style.cssText = `
|
|
7347
|
-
padding: 8px 0; border-radius: 6px; border: 1px solid ${col}88;
|
|
7348
|
-
background: ${col}14; color: ${col}; font-size: 13px; font-weight: 700;
|
|
7349
|
-
cursor: pointer; transition: transform 0.12s, background 0.15s;
|
|
7350
|
-
`;
|
|
7351
|
-
btn.textContent = String(n);
|
|
7352
|
-
btn.addEventListener("mouseenter", () => {
|
|
7353
|
-
btn.style.background = col;
|
|
7354
|
-
btn.style.color = "#fff";
|
|
7355
|
-
btn.style.transform = "translateY(-1px)";
|
|
7356
|
-
});
|
|
7357
|
-
btn.addEventListener("mouseleave", () => {
|
|
7358
|
-
btn.style.background = `${col}14`;
|
|
7359
|
-
btn.style.color = col;
|
|
7360
|
-
btn.style.transform = "translateY(0)";
|
|
7361
|
-
});
|
|
7362
|
-
btn.addEventListener("click", () => onPick(value));
|
|
7363
|
-
scale.appendChild(btn);
|
|
7364
|
-
}
|
|
7365
|
-
}
|
|
7366
|
-
body.appendChild(scale);
|
|
7367
|
-
let labels = null;
|
|
7368
|
-
if (isOverlay) {
|
|
7369
|
-
labels = document.createElement("div");
|
|
7370
|
-
labels.style.cssText = "display: flex; justify-content: space-between; font-size: 11px; opacity: 0.6; margin-bottom: 16px;";
|
|
7371
|
-
const notLikely = document.createElement("span");
|
|
7372
|
-
notLikely.textContent = "Not likely";
|
|
7373
|
-
const veryLikely = document.createElement("span");
|
|
7374
|
-
veryLikely.textContent = "Very likely";
|
|
7375
|
-
labels.appendChild(notLikely);
|
|
7376
|
-
labels.appendChild(veryLikely);
|
|
7377
|
-
body.appendChild(labels);
|
|
7378
|
-
}
|
|
7379
|
-
return body;
|
|
7380
|
-
}
|
|
7381
|
-
/**
|
|
7382
|
-
* NPS reason-capture step (overlay). Wires the per-band `nps_follow_up_*`
|
|
7383
|
-
* prompt (promoter / passive / detractor) as the heading and captures an
|
|
7384
|
-
* open-text reason (+ optional quick-pick chips). Submits {score, comment}
|
|
7385
|
-
* exactly ONCE (deferred from the pick), then shows the thank-you (promoters
|
|
7386
|
-
* get an optional review/referral CTA).
|
|
7387
|
-
*/
|
|
7388
|
-
_renderNpsReasonFollowup(campaign, ic, body, value, text) {
|
|
7389
|
-
this.addAnimationStyles();
|
|
7390
|
-
const promoterThreshold = ic.nps_promoter_threshold || 9;
|
|
7391
|
-
const prompt = value >= promoterThreshold ? ic.nps_follow_up_promoter || "What do you love most about us?" : value <= 6 ? ic.nps_follow_up_detractor || "How can we improve?" : ic.nps_follow_up_passive || "What would make us a 10?";
|
|
7392
|
-
while (body.firstChild) body.removeChild(body.firstChild);
|
|
7393
|
-
const heading = document.createElement("div");
|
|
7394
|
-
heading.style.cssText = "font-size: 15px; font-weight: 700; margin-bottom: 10px; text-align: center;";
|
|
7395
|
-
heading.textContent = prompt;
|
|
7396
|
-
body.appendChild(heading);
|
|
7397
|
-
const ta = document.createElement("textarea");
|
|
7398
|
-
const chips = Array.isArray(ic.nps_reason_chips) ? ic.nps_reason_chips : [];
|
|
7399
|
-
if (chips.length) {
|
|
7400
|
-
const chipRow = document.createElement("div");
|
|
7401
|
-
chipRow.style.cssText = "display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin-bottom: 10px;";
|
|
7402
|
-
chips.slice(0, 8).forEach((c) => {
|
|
7403
|
-
const chip = document.createElement("button");
|
|
7404
|
-
chip.type = "button";
|
|
7405
|
-
chip.textContent = c;
|
|
7406
|
-
chip.style.cssText = `padding: 5px 11px; border-radius: 999px; border: 1px solid ${text}33; background: transparent; color: ${text || "inherit"}; font-size: 12px; cursor: pointer;`;
|
|
7407
|
-
chip.addEventListener("click", () => {
|
|
7408
|
-
ta.value = ta.value ? `${ta.value}, ${c}` : c;
|
|
7409
|
-
ta.focus();
|
|
7410
|
-
});
|
|
7411
|
-
chipRow.appendChild(chip);
|
|
7412
|
-
});
|
|
7413
|
-
body.appendChild(chipRow);
|
|
7414
|
-
}
|
|
7415
|
-
ta.className = "aegis-rating-comment";
|
|
7416
|
-
ta.rows = 3;
|
|
7417
|
-
ta.placeholder = "Share a little more…";
|
|
7418
|
-
ta.style.cssText = `width: 100%; box-sizing: border-box; padding: 10px; border-radius: 10px; border: 1px solid ${text}33; background: transparent; color: ${text || "inherit"}; font-size: 13px; resize: vertical; margin-bottom: 12px; font-family: inherit;`;
|
|
7419
|
-
body.appendChild(ta);
|
|
7420
|
-
const st = this.inAppStyle(campaign);
|
|
7421
|
-
const row = document.createElement("div");
|
|
7422
|
-
row.style.cssText = "display: flex; gap: 8px; justify-content: center;";
|
|
7423
|
-
let done = false;
|
|
7424
|
-
const finish = (withComment) => {
|
|
7425
|
-
if (done) return;
|
|
7426
|
-
done = true;
|
|
7427
|
-
const comment = withComment ? ta.value.trim() : "";
|
|
7428
|
-
void this.submitWidgetResponse(campaign, "nps", comment ? { score: value, comment } : { score: value });
|
|
7429
|
-
this._renderNpsThankYou(campaign, ic, body, value >= promoterThreshold);
|
|
7430
|
-
};
|
|
7431
|
-
const send = document.createElement("button");
|
|
7432
|
-
send.type = "button";
|
|
7433
|
-
send.textContent = "Send";
|
|
7434
|
-
send.style.cssText = `padding: 9px 20px; border-radius: 999px; border: none; background: ${st.btnBg}; color: ${st.btnText}; font-weight: 800; font-size: 13px; cursor: pointer;`;
|
|
7435
|
-
send.addEventListener("click", () => finish(true));
|
|
7436
|
-
const skip = document.createElement("button");
|
|
7437
|
-
skip.type = "button";
|
|
7438
|
-
skip.textContent = "Skip";
|
|
7439
|
-
skip.style.cssText = `padding: 9px 16px; border-radius: 999px; border: 1px solid ${text}33; background: transparent; color: ${text || "inherit"}; font-weight: 600; font-size: 13px; cursor: pointer;`;
|
|
7440
|
-
skip.addEventListener("click", () => finish(false));
|
|
7441
|
-
row.appendChild(send);
|
|
7442
|
-
row.appendChild(skip);
|
|
7443
|
-
body.appendChild(row);
|
|
7444
|
-
ta.focus();
|
|
7445
|
-
}
|
|
7446
|
-
/** NPS thank-you takeover; promoters get an optional review/referral CTA. */
|
|
7447
|
-
_renderNpsThankYou(campaign, ic, body, isPromoter) {
|
|
7448
|
-
while (body.firstChild) body.removeChild(body.firstChild);
|
|
7449
|
-
const modalEl = body.parentElement;
|
|
7450
|
-
if (modalEl) modalEl.style.overflow = "visible";
|
|
7451
|
-
body.style.position = "relative";
|
|
7452
|
-
body.style.minHeight = "176px";
|
|
7453
|
-
body.style.display = "flex";
|
|
7454
|
-
body.style.flexDirection = "column";
|
|
7455
|
-
body.style.alignItems = "center";
|
|
7456
|
-
body.style.justifyContent = "center";
|
|
7457
|
-
const heading = document.createElement("div");
|
|
7458
|
-
heading.style.cssText = "font-size: 16px; font-weight: 700; margin-bottom: 8px; text-align: center;";
|
|
7459
|
-
heading.textContent = ic.thank_you_message || (isPromoter ? "Thanks — glad you love it!" : "Thanks for your feedback!");
|
|
7460
|
-
body.appendChild(heading);
|
|
7461
|
-
const reviewUrl = ic.nps_promoter_url;
|
|
7462
|
-
if (isPromoter && reviewUrl) {
|
|
7463
|
-
const sub = document.createElement("div");
|
|
7464
|
-
sub.style.cssText = "font-size: 13px; opacity: 0.8; margin-bottom: 14px; text-align: center;";
|
|
7465
|
-
sub.textContent = "Would you share that with others?";
|
|
7466
|
-
body.appendChild(sub);
|
|
7467
|
-
const st = this.inAppStyle(campaign);
|
|
7468
|
-
const cta = document.createElement("button");
|
|
7469
|
-
cta.type = "button";
|
|
7470
|
-
cta.textContent = campaign.button_text || "Leave a review";
|
|
7471
|
-
cta.style.cssText = `padding: 10px 24px; border-radius: 999px; border: none; background: ${st.btnBg}; color: ${st.btnText}; font-weight: 800; font-size: 13.5px; cursor: pointer;`;
|
|
7472
|
-
cta.addEventListener("click", () => {
|
|
7473
|
-
this.trackEvent(campaign.id, "clicked", { meta: { action: "nps_review_redirect" } });
|
|
7474
|
-
const safe = this.sanitizeUrl(reviewUrl);
|
|
7475
|
-
if (safe) this.navigateToCampaignAction(campaign, safe);
|
|
7476
|
-
});
|
|
7477
|
-
body.appendChild(cta);
|
|
7478
|
-
} else {
|
|
7479
|
-
this._playReaction(body, isPromoter ? "affirm" : "acknowledge", void 0, ic);
|
|
7480
|
-
}
|
|
7481
|
-
}
|
|
7482
7526
|
/**
|
|
7483
7527
|
* Evaluate the currently armed campaigns against a client-side event
|
|
7484
7528
|
* and render any that match their `client_trigger`.
|
|
@@ -7540,6 +7584,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7540
7584
|
this._displaySizeMul = this._sizeMul(
|
|
7541
7585
|
campaign.interactive_config
|
|
7542
7586
|
);
|
|
7587
|
+
this._ensureSurveyTemplate(campaign);
|
|
7543
7588
|
const interactiveSubTypes = /* @__PURE__ */ new Set([
|
|
7544
7589
|
"spin_wheel",
|
|
7545
7590
|
"scratch_card",
|
|
@@ -7561,6 +7606,17 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7561
7606
|
this.emit("campaign-shown", campaign);
|
|
7562
7607
|
return;
|
|
7563
7608
|
}
|
|
7609
|
+
{
|
|
7610
|
+
const ic2 = campaign.interactive_config || {};
|
|
7611
|
+
const multiCard2 = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
|
|
7612
|
+
const overlayCardType = campaign.type === "modal" || campaign.type === "banner" || campaign.type === "full_screen" || campaign.type === "half_interstitial";
|
|
7613
|
+
if (this._hasComposedTemplate(ic2) && !multiCard2 && overlayCardType) {
|
|
7614
|
+
this.renderComposedCardOverlay(campaign, ic2);
|
|
7615
|
+
this.trackEvent(campaign.id, "impression");
|
|
7616
|
+
this.emit("campaign-shown", campaign);
|
|
7617
|
+
return;
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7564
7620
|
switch (campaign.type) {
|
|
7565
7621
|
case "custom_html":
|
|
7566
7622
|
this.renderCustomHtml(campaign);
|
|
@@ -7701,19 +7757,17 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7701
7757
|
if (campaign.sub_type && renderGame(this.buildRenderContext(campaign), campaign.sub_type)) {
|
|
7702
7758
|
return;
|
|
7703
7759
|
}
|
|
7760
|
+
{
|
|
7761
|
+
const isMultiStepFlow = campaign.sub_type === "quiz" || campaign.sub_type === "multi_step_form";
|
|
7762
|
+
if (this._hasComposedTemplate(ic) && !isMultiStepFlow) {
|
|
7763
|
+
this.renderComposedCardOverlay(campaign, ic);
|
|
7764
|
+
return;
|
|
7765
|
+
}
|
|
7766
|
+
}
|
|
7704
7767
|
switch (campaign.sub_type) {
|
|
7705
|
-
case "nps_survey":
|
|
7706
|
-
this.renderNPSSurvey(campaign, ic, bg, text);
|
|
7707
|
-
break;
|
|
7708
7768
|
case "countdown_offer":
|
|
7709
7769
|
this.renderCountdownOffer(campaign, ic, bg, text);
|
|
7710
7770
|
break;
|
|
7711
|
-
case "star_rating":
|
|
7712
|
-
this.renderStarRating(campaign, ic, bg, text);
|
|
7713
|
-
break;
|
|
7714
|
-
case "quick_poll":
|
|
7715
|
-
this.renderQuickPoll(campaign, ic, bg, text);
|
|
7716
|
-
break;
|
|
7717
7771
|
case "quiz":
|
|
7718
7772
|
this.renderQuiz(campaign, ic, bg, text);
|
|
7719
7773
|
break;
|
|
@@ -7880,21 +7934,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7880
7934
|
return true;
|
|
7881
7935
|
}
|
|
7882
7936
|
// --- Interactive Renderers ---
|
|
7883
|
-
renderNPSSurvey(campaign, ic, bg, text) {
|
|
7884
|
-
const overlay = this.createOverlay("aegis-in-app-nps-overlay");
|
|
7885
|
-
const modal = document.createElement("div");
|
|
7886
|
-
modal.style.cssText = `
|
|
7887
|
-
max-width: 360px; width: 90%; border-radius: 16px; overflow: hidden;
|
|
7888
|
-
background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
|
|
7889
|
-
box-shadow: 0 16px 48px rgba(0,0,0,0.22);
|
|
7890
|
-
`;
|
|
7891
|
-
const body = this._buildNPSSurveyBody(campaign, ic, text, "overlay");
|
|
7892
|
-
this.addCloseButton(body, overlay, campaign.id);
|
|
7893
|
-
modal.appendChild(body);
|
|
7894
|
-
overlay.appendChild(modal);
|
|
7895
|
-
this.addAnimationStyles();
|
|
7896
|
-
document.body.appendChild(overlay);
|
|
7897
|
-
}
|
|
7898
7937
|
renderCountdownOffer(campaign, ic, bg, text) {
|
|
7899
7938
|
const overlay = this.createOverlay("aegis-in-app-countdown-overlay");
|
|
7900
7939
|
const modal = document.createElement("div");
|
|
@@ -7982,84 +8021,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7982
8021
|
this.addAnimationStyles();
|
|
7983
8022
|
document.body.appendChild(overlay);
|
|
7984
8023
|
}
|
|
7985
|
-
renderStarRating(campaign, ic, bg, text) {
|
|
7986
|
-
const overlay = this.createOverlay("aegis-in-app-rating-overlay");
|
|
7987
|
-
const modal = document.createElement("div");
|
|
7988
|
-
modal.style.cssText = `
|
|
7989
|
-
max-width: 320px; width: 90%; border-radius: 16px; overflow: hidden;
|
|
7990
|
-
background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
|
|
7991
|
-
box-shadow: 0 16px 48px rgba(0,0,0,0.22);
|
|
7992
|
-
`;
|
|
7993
|
-
const body = this._buildStarRatingBody(campaign, ic, text, "overlay");
|
|
7994
|
-
this.addCloseButton(body, overlay, campaign.id);
|
|
7995
|
-
modal.appendChild(body);
|
|
7996
|
-
overlay.appendChild(modal);
|
|
7997
|
-
this.addAnimationStyles();
|
|
7998
|
-
document.body.appendChild(overlay);
|
|
7999
|
-
}
|
|
8000
|
-
renderQuickPoll(campaign, ic, bg, text) {
|
|
8001
|
-
const overlay = this.createOverlay("aegis-in-app-poll-overlay");
|
|
8002
|
-
const modal = document.createElement("div");
|
|
8003
|
-
modal.style.cssText = `
|
|
8004
|
-
max-width: 320px; width: 90%; border-radius: 16px; overflow: hidden;
|
|
8005
|
-
background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
|
|
8006
|
-
box-shadow: 0 16px 48px rgba(0,0,0,0.22);
|
|
8007
|
-
`;
|
|
8008
|
-
const body = document.createElement("div");
|
|
8009
|
-
body.style.cssText = "padding: 24px; text-align: center;";
|
|
8010
|
-
const title = document.createElement("div");
|
|
8011
|
-
title.style.cssText = `font-size: 16px; font-weight: 700; margin-bottom: ${campaign.body ? "6px" : "16px"};`;
|
|
8012
|
-
title.textContent = campaign.title || "Quick question";
|
|
8013
|
-
body.appendChild(title);
|
|
8014
|
-
if (campaign.body) {
|
|
8015
|
-
const sub = document.createElement("div");
|
|
8016
|
-
sub.style.cssText = `font-size: 13px; opacity: 0.8; margin-bottom: 16px; color: ${text};`;
|
|
8017
|
-
sub.textContent = campaign.body;
|
|
8018
|
-
body.appendChild(sub);
|
|
8019
|
-
}
|
|
8020
|
-
const options = ic.poll_options || [];
|
|
8021
|
-
const optionsList = document.createElement("div");
|
|
8022
|
-
optionsList.style.cssText = "display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px;";
|
|
8023
|
-
const buttons = [];
|
|
8024
|
-
options.forEach((opt, i) => {
|
|
8025
|
-
const optBtn = document.createElement("button");
|
|
8026
|
-
optBtn.style.cssText = `
|
|
8027
|
-
position: relative; overflow: hidden;
|
|
8028
|
-
padding: 10px 16px; border-radius: 10px; border: 1px solid ${text}33;
|
|
8029
|
-
background: transparent; color: ${text}; font-size: 14px; cursor: pointer;
|
|
8030
|
-
text-align: left; transition: border-color 0.2s ease;
|
|
8031
|
-
`;
|
|
8032
|
-
const lbl = document.createElement("span");
|
|
8033
|
-
lbl.style.cssText = "position: relative; z-index: 1; display: flex; justify-content: space-between; gap: 8px;";
|
|
8034
|
-
const lblText = document.createElement("span");
|
|
8035
|
-
lblText.textContent = opt;
|
|
8036
|
-
const lblPct = document.createElement("span");
|
|
8037
|
-
lblPct.style.cssText = "font-weight: 700; opacity: 0; transition: opacity 0.3s ease;";
|
|
8038
|
-
lbl.appendChild(lblText);
|
|
8039
|
-
lbl.appendChild(lblPct);
|
|
8040
|
-
optBtn.appendChild(lbl);
|
|
8041
|
-
optBtn.addEventListener("click", () => {
|
|
8042
|
-
this.trackEvent(campaign.id, "clicked");
|
|
8043
|
-
buttons.forEach((b) => b.disabled = true);
|
|
8044
|
-
this._paintPollResult(buttons, options, i, null, text);
|
|
8045
|
-
void this.submitWidgetResponse(campaign, "poll", { option_index: i, option_label: opt }).then((resp) => {
|
|
8046
|
-
const dist = this._readPollDistribution(resp, options.length);
|
|
8047
|
-
if (dist) this._paintPollResult(buttons, options, i, dist, text);
|
|
8048
|
-
});
|
|
8049
|
-
const modalEl = body.parentElement;
|
|
8050
|
-
if (modalEl) modalEl.style.overflow = "visible";
|
|
8051
|
-
this._playReaction(body, "affirm", "check.json", ic);
|
|
8052
|
-
});
|
|
8053
|
-
buttons.push(optBtn);
|
|
8054
|
-
optionsList.appendChild(optBtn);
|
|
8055
|
-
});
|
|
8056
|
-
body.appendChild(optionsList);
|
|
8057
|
-
this.addCloseButton(body, overlay, campaign.id);
|
|
8058
|
-
modal.appendChild(body);
|
|
8059
|
-
overlay.appendChild(modal);
|
|
8060
|
-
this.addAnimationStyles();
|
|
8061
|
-
document.body.appendChild(overlay);
|
|
8062
|
-
}
|
|
8063
8024
|
/**
|
|
8064
8025
|
* Multi-step form (SDK Demo v4 W1b) — in-app render type matching the
|
|
8065
8026
|
* Plotline multi-step flow pattern: a progress bar + per-step question with
|
|
@@ -8091,6 +8052,13 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8091
8052
|
const total = steps.length;
|
|
8092
8053
|
let current = 0;
|
|
8093
8054
|
const answers = new Array(total).fill(null);
|
|
8055
|
+
const flow = createFlowAccumulator();
|
|
8056
|
+
const viewedSteps = /* @__PURE__ */ new Set();
|
|
8057
|
+
const fireStepViewed = (idx) => {
|
|
8058
|
+
if (viewedSteps.has(idx)) return;
|
|
8059
|
+
viewedSteps.add(idx);
|
|
8060
|
+
void this.trackEvent(campaign.id, "impression", { stepId: `step_${idx + 1}` });
|
|
8061
|
+
};
|
|
8094
8062
|
const progressTrack = document.createElement("div");
|
|
8095
8063
|
progressTrack.style.cssText = `height: 4px; border-radius: 2px; background: ${text}22; margin-bottom: 16px; overflow: hidden;`;
|
|
8096
8064
|
const progressFill = document.createElement("div");
|
|
@@ -8120,6 +8088,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8120
8088
|
const step = steps[current] || {};
|
|
8121
8089
|
const q = step.question || step.label || `Step ${current + 1}`;
|
|
8122
8090
|
const opts = Array.isArray(step.options) ? step.options : [];
|
|
8091
|
+
fireStepViewed(current);
|
|
8123
8092
|
progressFill.style.width = `${Math.round(current / total * 100)}%`;
|
|
8124
8093
|
stepLabel.textContent = `Step ${current + 1} of ${total}`;
|
|
8125
8094
|
question.textContent = q;
|
|
@@ -8131,6 +8100,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8131
8100
|
ob.style.cssText = `padding: 10px 16px; border-radius: 10px; border: 1px solid ${text}33; background: ${selected ? text + "22" : "transparent"}; color: ${text}; font-size: 14px; cursor: pointer; text-align: left;`;
|
|
8132
8101
|
ob.addEventListener("click", () => {
|
|
8133
8102
|
answers[current] = opt;
|
|
8103
|
+
flow.recordStep({ step_id: `step_${current + 1}`, response_type: "form_step", payload: { question: q, answer: opt } });
|
|
8134
8104
|
renderStep();
|
|
8135
8105
|
});
|
|
8136
8106
|
optionsList.appendChild(ob);
|
|
@@ -8154,8 +8124,10 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8154
8124
|
renderStep();
|
|
8155
8125
|
} else {
|
|
8156
8126
|
progressFill.style.width = "100%";
|
|
8127
|
+
flow.markCompleted();
|
|
8128
|
+
void this.trackEvent(campaign.id, "impression", { stepId: "flow_completed" });
|
|
8157
8129
|
void this.trackEvent(campaign.id, "clicked", { meta: { answers } });
|
|
8158
|
-
void this.submitWidgetResponse(campaign, "form", { answers });
|
|
8130
|
+
void this.submitWidgetResponse(campaign, "form", { answers, flow: flow.consolidated() });
|
|
8159
8131
|
this._playReaction(body, "affirm", "check.json", ic);
|
|
8160
8132
|
window.setTimeout(() => {
|
|
8161
8133
|
if (overlay.parentNode) document.body.removeChild(overlay);
|