@active-reach/web-sdk 1.25.0 → 1.27.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/inapp/AegisInAppManager.d.ts +26 -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 +32 -0
- 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 +48 -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/inapp/renderers/types.d.ts +4 -1
- package/dist/inapp/renderers/types.d.ts.map +1 -1
- package/dist/index.js +394 -714
- package/dist/index.js.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",
|
|
@@ -436,7 +506,6 @@ function buildCountdownElement(el, fg, muted) {
|
|
|
436
506
|
time.style.cssText = `font-size:15px;font-weight:700;color:${fg};letter-spacing:0.2px;`;
|
|
437
507
|
wrap.appendChild(label);
|
|
438
508
|
wrap.appendChild(time);
|
|
439
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
440
509
|
let started = false;
|
|
441
510
|
let timer = 0;
|
|
442
511
|
const tick = () => {
|
|
@@ -444,25 +513,71 @@ function buildCountdownElement(el, fg, muted) {
|
|
|
444
513
|
if (timer) clearInterval(timer);
|
|
445
514
|
return;
|
|
446
515
|
}
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (timer) clearInterval(timer);
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
const s = Math.floor(ms / 1e3);
|
|
454
|
-
const d = Math.floor(s / 86400);
|
|
455
|
-
const h = Math.floor(s % 86400 / 3600);
|
|
456
|
-
const m2 = Math.floor(s % 3600 / 60);
|
|
457
|
-
const ss = s % 60;
|
|
458
|
-
time.textContent = d > 0 ? `${d}d ${pad(h)}:${pad(m2)}:${pad(ss)}` : `${pad(h)}:${pad(m2)}:${pad(ss)}`;
|
|
516
|
+
const cd = computeCountdown(target, Date.now());
|
|
517
|
+
time.textContent = cd.display;
|
|
518
|
+
if (cd.expired && timer) clearInterval(timer);
|
|
459
519
|
started = true;
|
|
460
520
|
};
|
|
461
521
|
tick();
|
|
462
522
|
timer = setInterval(tick, 1e3);
|
|
463
523
|
return wrap;
|
|
464
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
|
+
}
|
|
465
579
|
function buildRatingInputElement(el, ctx, fg) {
|
|
580
|
+
const accent = ctx.sanitizeColor(ctx.campaign.background_color || "#4169e1");
|
|
466
581
|
const wrap = document.createElement("div");
|
|
467
582
|
wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
|
|
468
583
|
if (el.content) {
|
|
@@ -505,9 +620,14 @@ function buildRatingInputElement(el, ctx, fg) {
|
|
|
505
620
|
submitted = true;
|
|
506
621
|
paint(i);
|
|
507
622
|
ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "rating_input", stars: i } });
|
|
508
|
-
|
|
509
|
-
(
|
|
510
|
-
|
|
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
|
+
}
|
|
511
631
|
});
|
|
512
632
|
row.appendChild(btn);
|
|
513
633
|
}
|
|
@@ -566,6 +686,85 @@ function buildPollElement(el, ctx, fg, accent) {
|
|
|
566
686
|
});
|
|
567
687
|
return wrap;
|
|
568
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
|
+
}
|
|
569
768
|
function buildNpsElement(el, ctx, fg, accent, muted) {
|
|
570
769
|
const wrap = document.createElement("div");
|
|
571
770
|
wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
|
|
@@ -601,10 +800,15 @@ function buildNpsElement(el, ctx, fg, accent, muted) {
|
|
|
601
800
|
btn.style.background = accent;
|
|
602
801
|
btn.style.color = "#fff";
|
|
603
802
|
btn.style.opacity = "1";
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
(
|
|
607
|
-
|
|
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
|
+
}
|
|
608
812
|
});
|
|
609
813
|
row.appendChild(btn);
|
|
610
814
|
}
|
|
@@ -642,9 +846,8 @@ function buildProgressElement(el, ctx, accent, muted) {
|
|
|
642
846
|
return 0;
|
|
643
847
|
};
|
|
644
848
|
const readCur = () => {
|
|
645
|
-
const
|
|
646
|
-
if (
|
|
647
|
-
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;
|
|
648
851
|
const p = props();
|
|
649
852
|
if (goal === "loyalty_points") return Number(p["loyalty.points"]) || 0;
|
|
650
853
|
if (goal === "referrals") return Number(p["referrals.count"]) || 0;
|
|
@@ -666,10 +869,18 @@ function buildProgressElement(el, ctx, accent, muted) {
|
|
|
666
869
|
var _a;
|
|
667
870
|
const th = threshold();
|
|
668
871
|
const cur = readCur();
|
|
669
|
-
const pct
|
|
872
|
+
const { pct, complete } = computeProgress({ threshold: th, current: cur });
|
|
670
873
|
bar.style.width = `${pct}%`;
|
|
671
|
-
|
|
672
|
-
|
|
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) {
|
|
673
884
|
sub.style.color = "#10b981";
|
|
674
885
|
sub.style.fontWeight = "700";
|
|
675
886
|
if (!firedUnlock) {
|
|
@@ -681,10 +892,6 @@ function buildProgressElement(el, ctx, accent, muted) {
|
|
|
681
892
|
sub.style.color = muted;
|
|
682
893
|
sub.style.fontWeight = "";
|
|
683
894
|
firedUnlock = false;
|
|
684
|
-
const remaining = Math.max(0, th - cur);
|
|
685
|
-
const pre = moneyGoal ? sym() : "";
|
|
686
|
-
const pctStr = el.show_pct && th > 0 ? ` · ${Math.round(pct)}%` : "";
|
|
687
|
-
sub.textContent = th > 0 ? `Shop for ${pre}${remaining.toFixed(0)} more${pctStr}` : el.reward_text || "";
|
|
688
895
|
}
|
|
689
896
|
};
|
|
690
897
|
update();
|
|
@@ -935,6 +1142,22 @@ function renderCardFromTemplate(card, opts) {
|
|
|
935
1142
|
}
|
|
936
1143
|
};
|
|
937
1144
|
if (inline || card.cta_url) tile.addEventListener("click", open);
|
|
1145
|
+
if (frame.celebrate_on_show === true && ctx.playReaction) {
|
|
1146
|
+
const reaction = frame.celebrate_reaction || "celebrate";
|
|
1147
|
+
const cscope = frame.celebrate_scope === "viewport" ? "viewport" : "card";
|
|
1148
|
+
setTimeout(() => {
|
|
1149
|
+
var _a;
|
|
1150
|
+
(_a = ctx.playReaction) == null ? void 0 : _a.call(ctx, tile, reaction, frame.celebrate_lottie, cscope);
|
|
1151
|
+
if (cscope === "viewport") {
|
|
1152
|
+
try {
|
|
1153
|
+
if (typeof sessionStorage !== "undefined") {
|
|
1154
|
+
sessionStorage.setItem("aegis:celebrated", String(Date.now()));
|
|
1155
|
+
}
|
|
1156
|
+
} catch {
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}, 0);
|
|
1160
|
+
}
|
|
938
1161
|
return tile;
|
|
939
1162
|
}
|
|
940
1163
|
const MAX_CARDS_RENDERED = 10;
|
|
@@ -1011,6 +1234,9 @@ function renderCarouselCards(ctx) {
|
|
|
1011
1234
|
layoutAccent: accent
|
|
1012
1235
|
});
|
|
1013
1236
|
tile.setAttribute("data-card-index", String(i));
|
|
1237
|
+
tile.addEventListener("click", () => {
|
|
1238
|
+
trackEvent(campaign.id, "clicked", { stepId: `card_${i}` });
|
|
1239
|
+
});
|
|
1014
1240
|
track.appendChild(tile);
|
|
1015
1241
|
});
|
|
1016
1242
|
card.appendChild(track);
|
|
@@ -1311,21 +1537,23 @@ function buildProgressBar(ctx, inline) {
|
|
|
1311
1537
|
};
|
|
1312
1538
|
const update = () => {
|
|
1313
1539
|
const cur = readCurrentValue();
|
|
1314
|
-
const pct
|
|
1540
|
+
const { pct, complete, remaining, milestonesReached } = computeProgress({
|
|
1541
|
+
threshold,
|
|
1542
|
+
current: cur
|
|
1543
|
+
});
|
|
1315
1544
|
fillEl.style.width = `${pct}%`;
|
|
1316
1545
|
numeric.textContent = `${cur.toFixed(0)} / ${threshold.toFixed(0)} · ${Math.round(pct)}%`;
|
|
1317
|
-
shimmer.style.display =
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
if (
|
|
1546
|
+
shimmer.style.display = complete ? "none" : "";
|
|
1547
|
+
for (const m2 of milestonesReached) {
|
|
1548
|
+
if (m2 !== "unlocked") fireMilestone(m2);
|
|
1549
|
+
}
|
|
1550
|
+
if (complete) {
|
|
1322
1551
|
footnote.textContent = rewardText;
|
|
1323
1552
|
if (!lastFiredUnlock) {
|
|
1324
1553
|
lastFiredUnlock = true;
|
|
1325
1554
|
trackEvent(campaign.id, "clicked", { stepId: "unlocked" });
|
|
1326
1555
|
}
|
|
1327
1556
|
} else {
|
|
1328
|
-
const remaining = Math.max(0, threshold - cur);
|
|
1329
1557
|
footnote.textContent = `Add ${remaining.toFixed(0)} more to unlock: ${rewardText}`;
|
|
1330
1558
|
}
|
|
1331
1559
|
};
|
|
@@ -2244,6 +2472,9 @@ function injectStoryStyles() {
|
|
|
2244
2472
|
`;
|
|
2245
2473
|
document.head.appendChild(style);
|
|
2246
2474
|
}
|
|
2475
|
+
function earnPct(current, threshold) {
|
|
2476
|
+
return computeProgress({ threshold, current }).pct;
|
|
2477
|
+
}
|
|
2247
2478
|
const num = (v, d) => {
|
|
2248
2479
|
const n = Number(v);
|
|
2249
2480
|
return Number.isFinite(n) ? n : d;
|
|
@@ -2286,7 +2517,8 @@ function openGameModal(ctx, className, maxWidth = 360) {
|
|
|
2286
2517
|
};
|
|
2287
2518
|
const close = document.createElement("button");
|
|
2288
2519
|
close.type = "button";
|
|
2289
|
-
|
|
2520
|
+
const closeIcon = lucideSvgEl("x", { size: 15 });
|
|
2521
|
+
if (closeIcon) close.appendChild(closeIcon);
|
|
2290
2522
|
close.setAttribute("aria-label", "Close");
|
|
2291
2523
|
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;`;
|
|
2292
2524
|
close.addEventListener("click", () => dismiss(true));
|
|
@@ -2378,7 +2610,7 @@ function renderMilestone(ctx) {
|
|
|
2378
2610
|
const cur = num(ic.milestone_current, 1200);
|
|
2379
2611
|
const target = num(ic.milestone_target, 2e3);
|
|
2380
2612
|
const steps = arr(ic.milestone_steps);
|
|
2381
|
-
const pct =
|
|
2613
|
+
const pct = earnPct(cur, target);
|
|
2382
2614
|
s.content.appendChild(gEl("div", "font-size:13px;opacity:0.8;margin-bottom:2px", `${unit}${cur} of ${unit}${target}`));
|
|
2383
2615
|
s.content.appendChild(fillBar(s.accent, pct));
|
|
2384
2616
|
s.content.appendChild(gEl("div", "font-size:13px;font-weight:600", `${unit}${Math.max(0, target - cur)} to go`));
|
|
@@ -2399,7 +2631,7 @@ function renderChallenge(ctx) {
|
|
|
2399
2631
|
const s = openGameModal(ctx, "aegis-in-app-challenge");
|
|
2400
2632
|
const tasks = arr(ic.challenge_tasks);
|
|
2401
2633
|
const done = tasks.filter((t) => t.done).length;
|
|
2402
|
-
const pct = tasks.length ? done
|
|
2634
|
+
const pct = tasks.length ? earnPct(done, tasks.length) : 0;
|
|
2403
2635
|
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)));
|
|
2404
2636
|
tasks.forEach((t) => {
|
|
2405
2637
|
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");
|
|
@@ -2610,7 +2842,7 @@ function renderStampCard(ctx) {
|
|
|
2610
2842
|
const nextReward = [...rewardAt.keys()].filter((p) => p > filled).sort((a, b) => a - b)[0];
|
|
2611
2843
|
const progressTxt = nextReward ? `${nextReward - filled} more → ${rewardAt.get(nextReward)}` : "Card complete!";
|
|
2612
2844
|
const sub = gEl("div", "font-size:13px;font-weight:600;margin-top:2px");
|
|
2613
|
-
sub.appendChild(fillBar(ink, filled
|
|
2845
|
+
sub.appendChild(fillBar(ink, earnPct(filled, total)));
|
|
2614
2846
|
sub.appendChild(gEl("div", "", `${filled} of ${total}`));
|
|
2615
2847
|
sub.appendChild(gEl("div", "font-weight:400;opacity:0.82;margin-top:1px", str$1(ic.stamp_reward_text) || progressTxt));
|
|
2616
2848
|
s.content.appendChild(sub);
|
|
@@ -2713,7 +2945,7 @@ function renderTierLadder(ctx) {
|
|
|
2713
2945
|
const curIdx = Math.min(list.length - 1, num(ic.tier_current_index, 1));
|
|
2714
2946
|
const curPts = num(ic.tier_current_points, 640);
|
|
2715
2947
|
const nextPts = num(ic.tier_next_points, 1e3);
|
|
2716
|
-
const pct =
|
|
2948
|
+
const pct = earnPct(curPts, nextPts);
|
|
2717
2949
|
const perks = arr(ic.tier_perks);
|
|
2718
2950
|
const next = list[Math.min(list.length - 1, curIdx + 1)];
|
|
2719
2951
|
const ladder = gEl("div", "display:flex;align-items:flex-start;justify-content:center;margin:6px 0 12px");
|
|
@@ -4565,6 +4797,13 @@ let activeTriggers = null;
|
|
|
4565
4797
|
function str(v) {
|
|
4566
4798
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
4567
4799
|
}
|
|
4800
|
+
function resolveChatDisplayMode(raw) {
|
|
4801
|
+
var _a, _b;
|
|
4802
|
+
if (raw === "nav") return "nav";
|
|
4803
|
+
if (raw === "bubble") return "bubble";
|
|
4804
|
+
const isMobile = typeof window !== "undefined" && ((_b = (_a = window.matchMedia) == null ? void 0 : _a.call(window, "(max-width: 767px)")) == null ? void 0 : _b.matches) === true;
|
|
4805
|
+
return isMobile ? "nav" : "bubble";
|
|
4806
|
+
}
|
|
4568
4807
|
function renderActiveWebChat(ctx, creds) {
|
|
4569
4808
|
if (typeof document === "undefined") return null;
|
|
4570
4809
|
if (activeInstance) return activeInstance;
|
|
@@ -4584,10 +4823,9 @@ function renderActiveWebChat(ctx, creds) {
|
|
|
4584
4823
|
logoUrl: logo ? ctx.sanitizeUrl(logo) ?? void 0 : void 0,
|
|
4585
4824
|
position: ic.chat_position === "bottom-left" ? "bottom-left" : void 0,
|
|
4586
4825
|
surfaceStyle: ic.chat_theme === "tinted" ? "tinted" : void 0,
|
|
4587
|
-
//
|
|
4588
|
-
//
|
|
4589
|
-
|
|
4590
|
-
displayMode: ic.chat_display_mode === "nav" ? "nav" : void 0,
|
|
4826
|
+
// Explicit chat_display_mode wins; unset → responsive (phones = headless
|
|
4827
|
+
// 'nav' so the host's bottom-nav "Ask" tab is the entry; desktop = 'bubble').
|
|
4828
|
+
displayMode: resolveChatDisplayMode(ic.chat_display_mode),
|
|
4591
4829
|
agentPersona: str(ic.chat_agent_persona),
|
|
4592
4830
|
quickReplies: Array.isArray(ic.chat_quick_replies) ? ic.chat_quick_replies.filter((q) => typeof q === "string") : void 0,
|
|
4593
4831
|
// Bridge the engine's session lifecycle to campaign analytics. Per the
|
|
@@ -5107,6 +5345,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5107
5345
|
this.emitServedEvents(this.campaigns);
|
|
5108
5346
|
this.emit("campaigns-loaded", this.campaigns);
|
|
5109
5347
|
this.readyResolve();
|
|
5348
|
+
this.mountPersistentLaunchers();
|
|
5110
5349
|
this.tryDisplayNextCampaign();
|
|
5111
5350
|
this.renderIntoSlots();
|
|
5112
5351
|
this.renderIntoAnchors();
|
|
@@ -5170,12 +5409,28 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5170
5409
|
}
|
|
5171
5410
|
tryDisplayNextCampaign() {
|
|
5172
5411
|
const campaign = this.campaigns.find(
|
|
5173
|
-
(c) => !this.displayedCampaigns.has(c.id) && !c.client_trigger && !this.isSuppressed(c.id)
|
|
5412
|
+
(c) => !this.displayedCampaigns.has(c.id) && !c.client_trigger && !this.isSuppressed(c.id) && c.type !== "active_web_chat"
|
|
5174
5413
|
);
|
|
5175
5414
|
if (campaign) {
|
|
5176
5415
|
this.displayCampaign(campaign);
|
|
5177
5416
|
}
|
|
5178
5417
|
}
|
|
5418
|
+
/** Persistent launchers (active_web_chat) mount AMBIENTLY on every arm — a
|
|
5419
|
+
* single always-on entry point, NOT an interruption, so they must not
|
|
5420
|
+
* compete in the one-at-a-time overlay queue (tryDisplayNextCampaign).
|
|
5421
|
+
* Idempotent: renderActiveWebChatLauncher no-ops once a launcher is live and
|
|
5422
|
+
* displayedCampaigns guards re-display. Before this, the launcher only
|
|
5423
|
+
* mounted when it happened to be the single campaign tryDisplayNextCampaign
|
|
5424
|
+
* picked — so aegis.chat.open() / the mobile "Ask" tab silently did nothing
|
|
5425
|
+
* whenever another non-triggered campaign was armed first. */
|
|
5426
|
+
mountPersistentLaunchers() {
|
|
5427
|
+
for (const c of this.campaigns) {
|
|
5428
|
+
if (c.type !== "active_web_chat") continue;
|
|
5429
|
+
if (this.displayedCampaigns.has(c.id)) continue;
|
|
5430
|
+
if (this.isSuppressed(c.id)) continue;
|
|
5431
|
+
this.displayCampaign(c);
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5179
5434
|
/**
|
|
5180
5435
|
* Scan the page for `[data-aegis-slot]` anchors and render any
|
|
5181
5436
|
* campaign whose `widget_category` matches the slot key AND whose
|
|
@@ -5556,7 +5811,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5556
5811
|
target = sizer;
|
|
5557
5812
|
}
|
|
5558
5813
|
const { bg, text } = this._surfacePalette(campaign);
|
|
5559
|
-
const submitUrl = options == null ? void 0 : options.submitUrl;
|
|
5560
5814
|
const _formFields = Array.isArray(ic.form_fields) ? ic.form_fields : null;
|
|
5561
5815
|
let rendered = false;
|
|
5562
5816
|
if (_formFields && _formFields.length > 0) {
|
|
@@ -5571,43 +5825,22 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5571
5825
|
}
|
|
5572
5826
|
return;
|
|
5573
5827
|
}
|
|
5574
|
-
|
|
5575
|
-
const _hasComposedTemplate = !!_tpl && typeof _tpl === "object" && Array.isArray(_tpl.elements);
|
|
5828
|
+
this._ensureSurveyTemplate(campaign);
|
|
5576
5829
|
const _isMultiCard = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
|
|
5577
|
-
if (_hasComposedTemplate && !_isMultiCard) {
|
|
5830
|
+
if (this._hasComposedTemplate(ic) && !_isMultiCard) {
|
|
5578
5831
|
rendered = this.renderComposedCardSlot(campaign, ic, target);
|
|
5579
5832
|
} else if (campaign.widget_category === "hero") {
|
|
5580
5833
|
rendered = renderHeroInline(this.buildRenderContext(campaign), target);
|
|
5581
5834
|
} else
|
|
5582
5835
|
switch (campaign.sub_type) {
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
ic,
|
|
5587
|
-
bg,
|
|
5588
|
-
text,
|
|
5589
|
-
target,
|
|
5590
|
-
submitUrl
|
|
5591
|
-
);
|
|
5592
|
-
break;
|
|
5593
|
-
case "nps_survey":
|
|
5594
|
-
rendered = this.renderNPSSurveySlot(
|
|
5595
|
-
campaign,
|
|
5596
|
-
ic,
|
|
5597
|
-
bg,
|
|
5598
|
-
text,
|
|
5599
|
-
target,
|
|
5600
|
-
submitUrl
|
|
5601
|
-
);
|
|
5602
|
-
break;
|
|
5836
|
+
// P6.full — star_rating / nps_survey / quick_poll slots route through the
|
|
5837
|
+
// unified composed-card slot engine via `_ensureSurveyTemplate` (the gate
|
|
5838
|
+
// above), so the bespoke *Slot survey renderers are RETIRED.
|
|
5603
5839
|
// Parity tracker — built-in embedded gamification renderers (no
|
|
5604
5840
|
// AegisMessageRuntime callback needed, so they work on the bill).
|
|
5605
5841
|
case "spin_wheel":
|
|
5606
5842
|
rendered = this.renderSpinWheelSlot(campaign, ic, bg, text, target);
|
|
5607
5843
|
break;
|
|
5608
|
-
case "quick_poll":
|
|
5609
|
-
rendered = this.renderQuickPollSlot(campaign, ic, bg, text, target);
|
|
5610
|
-
break;
|
|
5611
5844
|
case "carousel_cards":
|
|
5612
5845
|
rendered = this.renderCarouselCardsSlot(campaign, ic, bg, text, target);
|
|
5613
5846
|
break;
|
|
@@ -5652,48 +5885,70 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
5652
5885
|
this.emit("campaign-shown", campaign);
|
|
5653
5886
|
}
|
|
5654
5887
|
}
|
|
5655
|
-
/**
|
|
5656
|
-
* Slot-mode wrappers. They build a small card container (no overlay
|
|
5657
|
-
* positioning, no close button) and append the SHARED body element
|
|
5658
|
-
* the overlay path also uses. Body construction lives in the
|
|
5659
|
-
* `_buildXxxBody` helpers below so visual output stays identical
|
|
5660
|
-
* between overlay and slot modes — adding a renderer feature in
|
|
5661
|
-
* one place updates both.
|
|
5662
|
-
*/
|
|
5663
|
-
renderStarRatingSlot(campaign, ic, bg, text, target, submitUrl) {
|
|
5664
|
-
const st = this._slotStyle(campaign);
|
|
5665
|
-
const card = this._wrapInSlotCard(
|
|
5666
|
-
"aegis-in-app-rating-card",
|
|
5667
|
-
campaign.id,
|
|
5668
|
-
bg,
|
|
5669
|
-
text,
|
|
5670
|
-
st.radius
|
|
5671
|
-
);
|
|
5672
|
-
card.appendChild(
|
|
5673
|
-
this._buildStarRatingBody(campaign, ic, text, "slot", submitUrl, st)
|
|
5674
|
-
);
|
|
5675
|
-
target.appendChild(card);
|
|
5676
|
-
return true;
|
|
5677
|
-
}
|
|
5678
|
-
renderNPSSurveySlot(campaign, ic, bg, text, target, submitUrl) {
|
|
5679
|
-
const st = this._slotStyle(campaign);
|
|
5680
|
-
const card = this._wrapInSlotCard(
|
|
5681
|
-
"aegis-in-app-nps-card",
|
|
5682
|
-
campaign.id,
|
|
5683
|
-
bg,
|
|
5684
|
-
text,
|
|
5685
|
-
st.radius
|
|
5686
|
-
);
|
|
5687
|
-
card.appendChild(
|
|
5688
|
-
this._buildNPSSurveyBody(campaign, ic, text, "slot", submitUrl, st)
|
|
5689
|
-
);
|
|
5690
|
-
target.appendChild(card);
|
|
5691
|
-
return true;
|
|
5692
|
-
}
|
|
5693
5888
|
/** §7.13.H — generic embedded card: image / title / body / coupon chip / CTA.
|
|
5694
5889
|
* The catch-all for banner + nudge sub_types (birthday, referral, app-install,
|
|
5695
5890
|
* win-back, etc.) so EVERY gallery nudge renders on the bill (previously only
|
|
5696
5891
|
* star_rating / nps_survey / form were handled → nudges never appeared). */
|
|
5892
|
+
/**
|
|
5893
|
+
* Whether `interactive_config` carries a composed `card_template` (non-empty
|
|
5894
|
+
* `elements[]`). The ONE detector shared by all three composed-card cutover
|
|
5895
|
+
* gates — inline slot, chrome overlay, and the P6 survey-overlay cutover — so
|
|
5896
|
+
* they can't drift (the inline gate previously omitted the non-empty check).
|
|
5897
|
+
*/
|
|
5898
|
+
_hasComposedTemplate(ic) {
|
|
5899
|
+
const tpl = ic.card_template;
|
|
5900
|
+
return !!tpl && typeof tpl === "object" && Array.isArray(tpl.elements) && tpl.elements.length > 0;
|
|
5901
|
+
}
|
|
5902
|
+
/**
|
|
5903
|
+
* P6.full — flat survey → card_template adapter. A `nps_survey` / `star_rating`
|
|
5904
|
+
* / `quick_poll` authored WITHOUT a card_template gets one synthesised in place
|
|
5905
|
+
* (carrying its follow-up config), so the EXISTING composed-template gates route
|
|
5906
|
+
* it through the ONE card-template engine + the shared element builders (with
|
|
5907
|
+
* the ported reason-capture/thank-you flows) — overlay AND slot. Back-compat:
|
|
5908
|
+
* the field names + follow-up defaults match the retired bespoke renderers.
|
|
5909
|
+
* No-op when a template already exists or the type isn't a single-card survey.
|
|
5910
|
+
*/
|
|
5911
|
+
_ensureSurveyTemplate(campaign) {
|
|
5912
|
+
const ic = campaign.interactive_config || {};
|
|
5913
|
+
if (this._hasComposedTemplate(ic)) return;
|
|
5914
|
+
let element = null;
|
|
5915
|
+
if (campaign.sub_type === "nps_survey") {
|
|
5916
|
+
element = {
|
|
5917
|
+
type: "nps",
|
|
5918
|
+
content: ic.question,
|
|
5919
|
+
thanks_text: ic.thank_you_message,
|
|
5920
|
+
nps_followup_enabled: ic.nps_followup_enabled !== false,
|
|
5921
|
+
nps_follow_up_promoter: ic.nps_follow_up_promoter,
|
|
5922
|
+
nps_follow_up_passive: ic.nps_follow_up_passive,
|
|
5923
|
+
nps_follow_up_detractor: ic.nps_follow_up_detractor,
|
|
5924
|
+
nps_reason_chips: ic.nps_reason_chips,
|
|
5925
|
+
nps_promoter_threshold: ic.nps_promoter_threshold,
|
|
5926
|
+
nps_promoter_url: ic.nps_promoter_url,
|
|
5927
|
+
nps_promoter_cta: ic.nps_promoter_cta
|
|
5928
|
+
};
|
|
5929
|
+
} else if (campaign.sub_type === "star_rating") {
|
|
5930
|
+
element = {
|
|
5931
|
+
type: "rating_input",
|
|
5932
|
+
content: ic.question,
|
|
5933
|
+
rating_scale: ic.rating_scale,
|
|
5934
|
+
thanks_text: ic.thank_you_message,
|
|
5935
|
+
rating_followup_enabled: ic.rating_followup_enabled !== false,
|
|
5936
|
+
rating_followup_threshold: ic.rating_followup_threshold,
|
|
5937
|
+
rating_followup_prompt: ic.rating_followup_prompt,
|
|
5938
|
+
rating_high_url: ic.rating_high_url ?? ic.nps_promoter_url,
|
|
5939
|
+
rating_high_cta: ic.rating_high_cta
|
|
5940
|
+
};
|
|
5941
|
+
} else if (campaign.sub_type === "quick_poll") {
|
|
5942
|
+
const opts = Array.isArray(ic.poll_options) ? ic.poll_options : Array.isArray(ic.options) ? ic.options : [];
|
|
5943
|
+
if (opts.length >= 2) {
|
|
5944
|
+
element = { type: "poll", content: ic.question, poll_options: opts, thanks_text: ic.thank_you_message };
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
if (!element) return;
|
|
5948
|
+
const cleaned = Object.fromEntries(Object.entries(element).filter(([, v]) => v !== void 0));
|
|
5949
|
+
ic.card_template = { frame: {}, elements: [cleaned] };
|
|
5950
|
+
campaign.interactive_config = ic;
|
|
5951
|
+
}
|
|
5697
5952
|
/**
|
|
5698
5953
|
* Composed single card — renders a campaign's top-level `card_template`
|
|
5699
5954
|
* (frame + elements: icon/text/progress/image/cta/group/…) through the generic
|
|
@@ -6646,88 +6901,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
6646
6901
|
}
|
|
6647
6902
|
return true;
|
|
6648
6903
|
}
|
|
6649
|
-
/** Built-in embedded QUICK POLL — option buttons submit the chosen index. */
|
|
6650
|
-
renderQuickPollSlot(campaign, ic, bg, text, target) {
|
|
6651
|
-
const st = this._slotStyle(campaign);
|
|
6652
|
-
const card = this._wrapInSlotCard("aegis-in-app-poll-card", campaign.id, bg, text, st.radius);
|
|
6653
|
-
const opts = Array.isArray(ic.poll_options) ? ic.poll_options.filter((o) => typeof o === "string") : [];
|
|
6654
|
-
const body = document.createElement("div");
|
|
6655
|
-
body.style.cssText = `padding: ${st.padY ?? 12}px ${st.padX ?? 12}px; display: flex; flex-direction: column; gap: 6px;`;
|
|
6656
|
-
if (campaign.title) {
|
|
6657
|
-
const t = document.createElement("div");
|
|
6658
|
-
t.style.cssText = `font-size: 13px; font-weight: ${st.weight ?? "700"};`;
|
|
6659
|
-
t.textContent = campaign.title;
|
|
6660
|
-
body.appendChild(t);
|
|
6661
|
-
}
|
|
6662
|
-
const buttons = [];
|
|
6663
|
-
opts.forEach((opt, i) => {
|
|
6664
|
-
const ob = document.createElement("button");
|
|
6665
|
-
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;`;
|
|
6666
|
-
const lbl = document.createElement("span");
|
|
6667
|
-
lbl.style.cssText = "position: relative; z-index: 1; display: flex; justify-content: space-between; gap: 8px;";
|
|
6668
|
-
const lblText = document.createElement("span");
|
|
6669
|
-
lblText.textContent = opt;
|
|
6670
|
-
const lblPct = document.createElement("span");
|
|
6671
|
-
lblPct.style.cssText = "font-weight: 700; opacity: 0; transition: opacity 0.3s ease;";
|
|
6672
|
-
lbl.appendChild(lblText);
|
|
6673
|
-
lbl.appendChild(lblPct);
|
|
6674
|
-
ob.appendChild(lbl);
|
|
6675
|
-
ob.addEventListener("click", () => {
|
|
6676
|
-
void this.trackEvent(campaign.id, "clicked");
|
|
6677
|
-
buttons.forEach((b) => b.disabled = true);
|
|
6678
|
-
this._paintPollResult(buttons, opts, i, null, text);
|
|
6679
|
-
this.addAnimationStyles();
|
|
6680
|
-
void this.submitWidgetResponse(campaign, "poll", { option_index: i, option_label: opt }).then((resp) => {
|
|
6681
|
-
const dist = this._readPollDistribution(resp, opts.length);
|
|
6682
|
-
if (dist) this._paintPollResult(buttons, opts, i, dist, text);
|
|
6683
|
-
});
|
|
6684
|
-
this._playReaction(card, "affirm", "check.json", ic);
|
|
6685
|
-
});
|
|
6686
|
-
buttons.push(ob);
|
|
6687
|
-
body.appendChild(ob);
|
|
6688
|
-
});
|
|
6689
|
-
card.appendChild(body);
|
|
6690
|
-
target.appendChild(card);
|
|
6691
|
-
return true;
|
|
6692
|
-
}
|
|
6693
|
-
/** Pull a poll vote distribution out of the server response, if present.
|
|
6694
|
-
* Accepts `results`/`distribution`/`tallies` as a number[] aligned to the
|
|
6695
|
-
* option order. Returns null when the server didn't surface tallies (then
|
|
6696
|
-
* we only confirm the chosen option — never fabricate percentages). */
|
|
6697
|
-
_readPollDistribution(resp, count) {
|
|
6698
|
-
if (!resp) return null;
|
|
6699
|
-
const raw = resp.results ?? resp.distribution ?? resp.tallies;
|
|
6700
|
-
if (!Array.isArray(raw) || raw.length !== count) return null;
|
|
6701
|
-
const nums = raw.map((v) => typeof v === "number" && isFinite(v) && v >= 0 ? v : 0);
|
|
6702
|
-
return nums.some((n) => n > 0) ? nums : null;
|
|
6703
|
-
}
|
|
6704
|
-
/** Animate result bars on the poll options. With a real `dist` it renders
|
|
6705
|
-
* each option's share; without one it just fills the selected option 100%
|
|
6706
|
-
* as a "vote locked in" confirmation. */
|
|
6707
|
-
_paintPollResult(buttons, _opts, selected, dist, text) {
|
|
6708
|
-
const total = dist ? dist.reduce((a, b) => a + b, 0) || 1 : 1;
|
|
6709
|
-
buttons.forEach((ob, i) => {
|
|
6710
|
-
const pct = dist ? Math.round(dist[i] / total * 100) : i === selected ? 100 : 0;
|
|
6711
|
-
let fill = ob.querySelector(".aegis-poll-fill");
|
|
6712
|
-
if (!fill) {
|
|
6713
|
-
fill = document.createElement("span");
|
|
6714
|
-
fill.className = "aegis-poll-fill";
|
|
6715
|
-
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"};`;
|
|
6716
|
-
ob.insertBefore(fill, ob.firstChild);
|
|
6717
|
-
}
|
|
6718
|
-
fill.style.setProperty("--pct", `${pct}%`);
|
|
6719
|
-
fill.style.animation = "aegisPollFill 0.5s ease forwards";
|
|
6720
|
-
if (i === selected) ob.style.borderColor = text;
|
|
6721
|
-
const pctLabel = ob.querySelector("span > span:last-child");
|
|
6722
|
-
if (pctLabel && dist) {
|
|
6723
|
-
pctLabel.textContent = `${pct}%`;
|
|
6724
|
-
pctLabel.style.opacity = "0.8";
|
|
6725
|
-
} else if (pctLabel && i === selected) {
|
|
6726
|
-
pctLabel.innerHTML = lucideSvg("check", { size: 14 });
|
|
6727
|
-
pctLabel.style.opacity = "0.8";
|
|
6728
|
-
}
|
|
6729
|
-
});
|
|
6730
|
-
}
|
|
6731
6904
|
/**
|
|
6732
6905
|
* Built-in embedded IMAGE/VIDEO CAROUSEL — a swipeable horizontal strip of
|
|
6733
6906
|
* `interactive_config.cards` (image OR video + title + body + CTA), contained
|
|
@@ -7306,23 +7479,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7306
7479
|
for (const c of cands) if (typeof c === "string" && c) return c;
|
|
7307
7480
|
return null;
|
|
7308
7481
|
}
|
|
7309
|
-
/**
|
|
7310
|
-
* Token-bound submission helper. POSTs the structured response to
|
|
7311
|
-
* the per-token submit URL the page handler embedded. Best-effort
|
|
7312
|
-
* — failure is logged but doesn't throw (the customer already
|
|
7313
|
-
* clicked; we can't undo their interaction).
|
|
7314
|
-
*/
|
|
7315
|
-
_submitTokenResponse(submitUrl, payload) {
|
|
7316
|
-
if (!submitUrl) return;
|
|
7317
|
-
fetch(submitUrl, {
|
|
7318
|
-
method: "POST",
|
|
7319
|
-
headers: { "Content-Type": "application/json" },
|
|
7320
|
-
body: JSON.stringify({ response: payload }),
|
|
7321
|
-
keepalive: true
|
|
7322
|
-
}).catch((err) => {
|
|
7323
|
-
this.log(`token submit failed: ${err}`, "warn");
|
|
7324
|
-
});
|
|
7325
|
-
}
|
|
7326
7482
|
// --- Shared body builders (used by both overlay + slot paths) ----------
|
|
7327
7483
|
//
|
|
7328
7484
|
// Sizing: `'overlay'` matches the legacy fullscreen-modal scale (24px
|
|
@@ -7383,398 +7539,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7383
7539
|
el.style.boxShadow = "none";
|
|
7384
7540
|
}
|
|
7385
7541
|
}
|
|
7386
|
-
// Optional header/brand image atop a survey-style widget (star / nps).
|
|
7387
|
-
_appendHeaderImage(body, ic) {
|
|
7388
|
-
const url = ic.header_image_url;
|
|
7389
|
-
if (!url) return;
|
|
7390
|
-
const safe = this.sanitizeUrl(url);
|
|
7391
|
-
if (!safe) return;
|
|
7392
|
-
const img = document.createElement("img");
|
|
7393
|
-
img.src = safe;
|
|
7394
|
-
img.alt = "";
|
|
7395
|
-
img.style.cssText = "display: block; max-height: 72px; max-width: 60%; margin: 0 auto 14px; object-fit: contain;";
|
|
7396
|
-
body.appendChild(img);
|
|
7397
|
-
}
|
|
7398
|
-
_buildStarRatingBody(campaign, ic, text, variant, submitUrl, slotStyle) {
|
|
7399
|
-
const isOverlay = variant === "overlay";
|
|
7400
|
-
const padding = isOverlay ? "24px" : `${(slotStyle == null ? void 0 : slotStyle.padY) ?? 20}px ${(slotStyle == null ? void 0 : slotStyle.padX) ?? 20}px`;
|
|
7401
|
-
const titleSize = isOverlay ? "18px" : "16px";
|
|
7402
|
-
const titleWeight = isOverlay ? "700" : (slotStyle == null ? void 0 : slotStyle.weight) ?? "600";
|
|
7403
|
-
const titleMargin = isOverlay ? "16px" : "12px";
|
|
7404
|
-
const starSize = isOverlay ? "32px" : "28px";
|
|
7405
|
-
const starsMarginBottom = isOverlay ? "16px" : "0";
|
|
7406
|
-
const hoverScale = isOverlay ? "1.2" : "1.15";
|
|
7407
|
-
const body = document.createElement("div");
|
|
7408
|
-
body.style.cssText = `padding: ${padding}; text-align: center;`;
|
|
7409
|
-
this._appendHeaderImage(body, ic);
|
|
7410
|
-
const title = document.createElement("div");
|
|
7411
|
-
title.style.cssText = `font-size: ${titleSize}; font-weight: ${titleWeight}; margin-bottom: ${titleMargin};`;
|
|
7412
|
-
title.textContent = campaign.title || "Rate your experience";
|
|
7413
|
-
body.appendChild(title);
|
|
7414
|
-
const stars = document.createElement("div");
|
|
7415
|
-
stars.style.cssText = `display: flex; gap: 8px; justify-content: center;` + (starsMarginBottom !== "0" ? ` margin-bottom: ${starsMarginBottom};` : "");
|
|
7416
|
-
const maxStars = ic.rating_scale || 5;
|
|
7417
|
-
const GOLD = "#fbbf24";
|
|
7418
|
-
const iconSet = ic.rating_icon || "star";
|
|
7419
|
-
const EMOJI = ["😞", "🙁", "😐", "🙂", "😀", "🤩"];
|
|
7420
|
-
const emojiFor = (idx) => {
|
|
7421
|
-
const k = maxStars <= 1 ? EMOJI.length - 1 : Math.round(idx / (maxStars - 1) * (EMOJI.length - 1));
|
|
7422
|
-
return EMOJI[Math.max(0, Math.min(EMOJI.length - 1, k))];
|
|
7423
|
-
};
|
|
7424
|
-
const glyph = (on, idx) => iconSet === "emoji" ? emojiFor(idx) : iconSet === "heart" ? on ? "♥" : "♡" : on ? "★" : "☆";
|
|
7425
|
-
const labels = Array.isArray(ic.rating_labels) ? ic.rating_labels : null;
|
|
7426
|
-
let caption2 = null;
|
|
7427
|
-
if (labels && labels.length > 0) {
|
|
7428
|
-
caption2 = document.createElement("div");
|
|
7429
|
-
caption2.style.cssText = `font-size: 12px; opacity: 0.7; margin-top: 8px; min-height: 16px; color: ${text || "inherit"};`;
|
|
7430
|
-
}
|
|
7431
|
-
const starEls = [];
|
|
7432
|
-
let selected = 0;
|
|
7433
|
-
const paint = (n) => {
|
|
7434
|
-
starEls.forEach((el, idx) => {
|
|
7435
|
-
const on = idx < n;
|
|
7436
|
-
el.textContent = glyph(on, idx);
|
|
7437
|
-
if (iconSet === "emoji") {
|
|
7438
|
-
el.style.opacity = on || n === 0 ? "1" : "0.3";
|
|
7439
|
-
el.style.color = "inherit";
|
|
7440
|
-
} else {
|
|
7441
|
-
el.style.color = on ? GOLD : text || "inherit";
|
|
7442
|
-
}
|
|
7443
|
-
el.style.transform = on && idx === n - 1 ? `scale(${hoverScale})` : "scale(1)";
|
|
7444
|
-
});
|
|
7445
|
-
if (caption2 && labels) {
|
|
7446
|
-
const lbl = n > 0 ? labels[Math.min(n - 1, labels.length - 1)] : "";
|
|
7447
|
-
caption2.textContent = lbl || "";
|
|
7448
|
-
}
|
|
7449
|
-
};
|
|
7450
|
-
for (let i = 1; i <= maxStars; i++) {
|
|
7451
|
-
const star = document.createElement("span");
|
|
7452
|
-
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;" : "");
|
|
7453
|
-
star.setAttribute("role", "button");
|
|
7454
|
-
star.setAttribute("aria-label", `${i} ${iconSet === "heart" ? "heart" : "star"}${i > 1 ? "s" : ""}`);
|
|
7455
|
-
star.textContent = glyph(false, i - 1);
|
|
7456
|
-
const value = i;
|
|
7457
|
-
star.addEventListener("mouseenter", () => paint(value));
|
|
7458
|
-
star.addEventListener("mouseleave", () => paint(selected));
|
|
7459
|
-
star.addEventListener("click", () => {
|
|
7460
|
-
selected = value;
|
|
7461
|
-
paint(selected);
|
|
7462
|
-
star.style.transform = "scale(1.28)";
|
|
7463
|
-
setTimeout(() => paint(selected), 160);
|
|
7464
|
-
this.trackEvent(campaign.id, "clicked");
|
|
7465
|
-
this._onStarRatingPicked(campaign, ic, body, value, maxStars, text, isOverlay, submitUrl);
|
|
7466
|
-
});
|
|
7467
|
-
starEls.push(star);
|
|
7468
|
-
stars.appendChild(star);
|
|
7469
|
-
}
|
|
7470
|
-
body.appendChild(stars);
|
|
7471
|
-
if (caption2) body.appendChild(caption2);
|
|
7472
|
-
return body;
|
|
7473
|
-
}
|
|
7474
|
-
/**
|
|
7475
|
-
* Post-rating flow — the Reelo/Plotline "smart rating" gate. A happy rater
|
|
7476
|
-
* (>= threshold) is invited to leave a PUBLIC review; an unhappy one is given
|
|
7477
|
-
* a PRIVATE "what went wrong?" comment box so the complaint is captured for
|
|
7478
|
-
* the operator instead of landing on a public platform. Exactly ONE response
|
|
7479
|
-
* row is written per rating (deferred on the low path until Send/Skip) so the
|
|
7480
|
-
* star distribution isn't double-counted.
|
|
7481
|
-
*/
|
|
7482
|
-
_onStarRatingPicked(campaign, ic, body, value, maxStars, text, isOverlay, submitUrl) {
|
|
7483
|
-
if (submitUrl) {
|
|
7484
|
-
this._submitTokenResponse(submitUrl, { sub_type: "star_rating", value });
|
|
7485
|
-
}
|
|
7486
|
-
const followupEnabled = isOverlay && ic.rating_followup_enabled !== false;
|
|
7487
|
-
const threshold = ic.rating_followup_threshold || Math.ceil(maxStars * 0.8);
|
|
7488
|
-
const isHigh = value >= threshold;
|
|
7489
|
-
if (!isOverlay) {
|
|
7490
|
-
if (isHigh) this._playReaction(body, "affirm", void 0, ic);
|
|
7491
|
-
else if (value <= Math.max(1, Math.floor(maxStars * 0.4))) this._playReaction(body, "empathize", void 0, ic);
|
|
7492
|
-
else this._playReaction(body, "acknowledge", void 0, ic);
|
|
7493
|
-
return;
|
|
7494
|
-
}
|
|
7495
|
-
if (!followupEnabled) {
|
|
7496
|
-
void this.submitWidgetResponse(campaign, "rating", { stars: value });
|
|
7497
|
-
this._renderRatingThankYou(campaign, ic, body, isHigh);
|
|
7498
|
-
return;
|
|
7499
|
-
}
|
|
7500
|
-
if (isHigh) {
|
|
7501
|
-
void this.submitWidgetResponse(campaign, "rating", { stars: value });
|
|
7502
|
-
this._renderRatingThankYou(campaign, ic, body, true);
|
|
7503
|
-
} else {
|
|
7504
|
-
this._renderRatingLowFollowup(campaign, ic, body, value, text);
|
|
7505
|
-
}
|
|
7506
|
-
}
|
|
7507
|
-
/** Thank-you takeover; happy raters also get a public-review CTA when authored. */
|
|
7508
|
-
_renderRatingThankYou(campaign, ic, body, isHigh) {
|
|
7509
|
-
while (body.firstChild) body.removeChild(body.firstChild);
|
|
7510
|
-
const modalEl = body.parentElement;
|
|
7511
|
-
if (modalEl) modalEl.style.overflow = "visible";
|
|
7512
|
-
body.style.position = "relative";
|
|
7513
|
-
body.style.minHeight = "176px";
|
|
7514
|
-
body.style.display = "flex";
|
|
7515
|
-
body.style.flexDirection = "column";
|
|
7516
|
-
body.style.alignItems = "center";
|
|
7517
|
-
body.style.justifyContent = "center";
|
|
7518
|
-
const heading = document.createElement("div");
|
|
7519
|
-
heading.style.cssText = "font-size: 16px; font-weight: 700; margin-bottom: 8px;";
|
|
7520
|
-
heading.textContent = isHigh ? ic.rating_high_prompt || "Thanks — glad you loved it!" : ic.thank_you_message || "Thanks for your feedback!";
|
|
7521
|
-
body.appendChild(heading);
|
|
7522
|
-
const reviewUrl = ic.rating_review_url;
|
|
7523
|
-
if (isHigh && reviewUrl) {
|
|
7524
|
-
const sub = document.createElement("div");
|
|
7525
|
-
sub.style.cssText = "font-size: 13px; opacity: 0.8; margin-bottom: 14px;";
|
|
7526
|
-
sub.textContent = "Would you share that with others?";
|
|
7527
|
-
body.appendChild(sub);
|
|
7528
|
-
const st = this.inAppStyle(campaign);
|
|
7529
|
-
const cta = document.createElement("button");
|
|
7530
|
-
cta.type = "button";
|
|
7531
|
-
cta.textContent = campaign.button_text || "Leave a review";
|
|
7532
|
-
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;`;
|
|
7533
|
-
cta.addEventListener("click", () => {
|
|
7534
|
-
this.trackEvent(campaign.id, "clicked", { meta: { action: "review_redirect" } });
|
|
7535
|
-
const safe = this.sanitizeUrl(reviewUrl);
|
|
7536
|
-
if (safe) this.navigateToCampaignAction(campaign, safe);
|
|
7537
|
-
});
|
|
7538
|
-
body.appendChild(cta);
|
|
7539
|
-
} else {
|
|
7540
|
-
this._playReaction(body, isHigh ? "affirm" : "acknowledge", void 0, ic);
|
|
7541
|
-
}
|
|
7542
|
-
}
|
|
7543
|
-
/** Unhappy-path private comment capture; submits {stars, comment} once. */
|
|
7544
|
-
_renderRatingLowFollowup(campaign, ic, body, value, text) {
|
|
7545
|
-
this.addAnimationStyles();
|
|
7546
|
-
while (body.firstChild) body.removeChild(body.firstChild);
|
|
7547
|
-
const heading = document.createElement("div");
|
|
7548
|
-
heading.style.cssText = "font-size: 15px; font-weight: 700; margin-bottom: 10px;";
|
|
7549
|
-
heading.textContent = ic.rating_low_prompt || "Sorry to hear that. What went wrong?";
|
|
7550
|
-
body.appendChild(heading);
|
|
7551
|
-
const ta = document.createElement("textarea");
|
|
7552
|
-
ta.className = "aegis-rating-comment";
|
|
7553
|
-
ta.rows = 3;
|
|
7554
|
-
ta.placeholder = "Tell us how we can do better…";
|
|
7555
|
-
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;`;
|
|
7556
|
-
body.appendChild(ta);
|
|
7557
|
-
const st = this.inAppStyle(campaign);
|
|
7558
|
-
const row = document.createElement("div");
|
|
7559
|
-
row.style.cssText = "display: flex; gap: 8px; justify-content: center;";
|
|
7560
|
-
let done = false;
|
|
7561
|
-
const finish = (withComment) => {
|
|
7562
|
-
if (done) return;
|
|
7563
|
-
done = true;
|
|
7564
|
-
const comment = withComment ? ta.value.trim() : "";
|
|
7565
|
-
void this.submitWidgetResponse(campaign, "rating", comment ? { stars: value, comment } : { stars: value });
|
|
7566
|
-
this._renderRatingThankYou(campaign, ic, body, false);
|
|
7567
|
-
};
|
|
7568
|
-
const send = document.createElement("button");
|
|
7569
|
-
send.type = "button";
|
|
7570
|
-
send.textContent = "Send feedback";
|
|
7571
|
-
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;`;
|
|
7572
|
-
send.addEventListener("click", () => finish(true));
|
|
7573
|
-
const skip = document.createElement("button");
|
|
7574
|
-
skip.type = "button";
|
|
7575
|
-
skip.textContent = "Skip";
|
|
7576
|
-
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;`;
|
|
7577
|
-
skip.addEventListener("click", () => finish(false));
|
|
7578
|
-
row.appendChild(send);
|
|
7579
|
-
row.appendChild(skip);
|
|
7580
|
-
body.appendChild(row);
|
|
7581
|
-
ta.focus();
|
|
7582
|
-
}
|
|
7583
|
-
_buildNPSSurveyBody(campaign, ic, text, variant, submitUrl, slotStyle) {
|
|
7584
|
-
const isOverlay = variant === "overlay";
|
|
7585
|
-
const padding = isOverlay ? "24px" : `${(slotStyle == null ? void 0 : slotStyle.padY) ?? 20}px ${(slotStyle == null ? void 0 : slotStyle.padX) ?? 20}px`;
|
|
7586
|
-
const titleAlign = isOverlay ? "center" : "center";
|
|
7587
|
-
const body = document.createElement("div");
|
|
7588
|
-
body.style.cssText = `padding: ${padding};` + (isOverlay ? " text-align: center;" : "");
|
|
7589
|
-
this._appendHeaderImage(body, ic);
|
|
7590
|
-
const title = document.createElement("div");
|
|
7591
|
-
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};`;
|
|
7592
|
-
title.textContent = ic.nps_question || campaign.title || "How likely are you to recommend us?";
|
|
7593
|
-
body.appendChild(title);
|
|
7594
|
-
const npsColor = (n) => n <= 6 ? "#ef4444" : n <= 8 ? "#f59e0b" : "#10b981";
|
|
7595
|
-
const scale = document.createElement("div");
|
|
7596
|
-
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;";
|
|
7597
|
-
const onPick = (value) => {
|
|
7598
|
-
this.trackEvent(campaign.id, "clicked");
|
|
7599
|
-
if (submitUrl) {
|
|
7600
|
-
this._submitTokenResponse(submitUrl, { sub_type: "nps_survey", value });
|
|
7601
|
-
}
|
|
7602
|
-
const followupEnabled = isOverlay && ic.nps_followup_enabled !== false;
|
|
7603
|
-
if (followupEnabled) {
|
|
7604
|
-
this._renderNpsReasonFollowup(campaign, ic, body, value, text);
|
|
7605
|
-
return;
|
|
7606
|
-
}
|
|
7607
|
-
void this.submitWidgetResponse(campaign, "nps", { score: value });
|
|
7608
|
-
this.addAnimationStyles();
|
|
7609
|
-
const thanks = document.createElement("div");
|
|
7610
|
-
thanks.style.cssText = `font-size: 14px; font-weight: 600; padding: 8px 0; text-align: center; color: ${text};`;
|
|
7611
|
-
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!";
|
|
7612
|
-
scale.replaceWith(thanks);
|
|
7613
|
-
labels == null ? void 0 : labels.remove();
|
|
7614
|
-
if (value >= 9) this._playReaction(body, "affirm", void 0, ic);
|
|
7615
|
-
else if (value <= 6) this._playReaction(body, "empathize", void 0, ic);
|
|
7616
|
-
else this._playReaction(body, "acknowledge", void 0, ic);
|
|
7617
|
-
};
|
|
7618
|
-
for (let n = 0; n <= 10; n++) {
|
|
7619
|
-
const col = npsColor(n);
|
|
7620
|
-
const value = n;
|
|
7621
|
-
if (isOverlay) {
|
|
7622
|
-
const btn = document.createElement("span");
|
|
7623
|
-
btn.style.cssText = `
|
|
7624
|
-
width: 28px; height: 28px; border-radius: 6px; display: flex;
|
|
7625
|
-
align-items: center; justify-content: center; font-size: 12px;
|
|
7626
|
-
font-weight: 700; cursor: pointer; background: ${col}; color: #fff;
|
|
7627
|
-
opacity: 0.85; transition: transform 0.1s, opacity 0.15s;
|
|
7628
|
-
`;
|
|
7629
|
-
btn.textContent = String(n);
|
|
7630
|
-
btn.addEventListener("mouseenter", () => {
|
|
7631
|
-
btn.style.opacity = "1";
|
|
7632
|
-
btn.style.transform = "translateY(-1px)";
|
|
7633
|
-
});
|
|
7634
|
-
btn.addEventListener("mouseleave", () => {
|
|
7635
|
-
btn.style.opacity = "0.85";
|
|
7636
|
-
btn.style.transform = "translateY(0)";
|
|
7637
|
-
});
|
|
7638
|
-
btn.addEventListener("click", () => onPick(value));
|
|
7639
|
-
scale.appendChild(btn);
|
|
7640
|
-
} else {
|
|
7641
|
-
const btn = document.createElement("button");
|
|
7642
|
-
btn.style.cssText = `
|
|
7643
|
-
padding: 8px 0; border-radius: 6px; border: 1px solid ${col}88;
|
|
7644
|
-
background: ${col}14; color: ${col}; font-size: 13px; font-weight: 700;
|
|
7645
|
-
cursor: pointer; transition: transform 0.12s, background 0.15s;
|
|
7646
|
-
`;
|
|
7647
|
-
btn.textContent = String(n);
|
|
7648
|
-
btn.addEventListener("mouseenter", () => {
|
|
7649
|
-
btn.style.background = col;
|
|
7650
|
-
btn.style.color = "#fff";
|
|
7651
|
-
btn.style.transform = "translateY(-1px)";
|
|
7652
|
-
});
|
|
7653
|
-
btn.addEventListener("mouseleave", () => {
|
|
7654
|
-
btn.style.background = `${col}14`;
|
|
7655
|
-
btn.style.color = col;
|
|
7656
|
-
btn.style.transform = "translateY(0)";
|
|
7657
|
-
});
|
|
7658
|
-
btn.addEventListener("click", () => onPick(value));
|
|
7659
|
-
scale.appendChild(btn);
|
|
7660
|
-
}
|
|
7661
|
-
}
|
|
7662
|
-
body.appendChild(scale);
|
|
7663
|
-
let labels = null;
|
|
7664
|
-
if (isOverlay) {
|
|
7665
|
-
labels = document.createElement("div");
|
|
7666
|
-
labels.style.cssText = "display: flex; justify-content: space-between; font-size: 11px; opacity: 0.6; margin-bottom: 16px;";
|
|
7667
|
-
const notLikely = document.createElement("span");
|
|
7668
|
-
notLikely.textContent = "Not likely";
|
|
7669
|
-
const veryLikely = document.createElement("span");
|
|
7670
|
-
veryLikely.textContent = "Very likely";
|
|
7671
|
-
labels.appendChild(notLikely);
|
|
7672
|
-
labels.appendChild(veryLikely);
|
|
7673
|
-
body.appendChild(labels);
|
|
7674
|
-
}
|
|
7675
|
-
return body;
|
|
7676
|
-
}
|
|
7677
|
-
/**
|
|
7678
|
-
* NPS reason-capture step (overlay). Wires the per-band `nps_follow_up_*`
|
|
7679
|
-
* prompt (promoter / passive / detractor) as the heading and captures an
|
|
7680
|
-
* open-text reason (+ optional quick-pick chips). Submits {score, comment}
|
|
7681
|
-
* exactly ONCE (deferred from the pick), then shows the thank-you (promoters
|
|
7682
|
-
* get an optional review/referral CTA).
|
|
7683
|
-
*/
|
|
7684
|
-
_renderNpsReasonFollowup(campaign, ic, body, value, text) {
|
|
7685
|
-
this.addAnimationStyles();
|
|
7686
|
-
const promoterThreshold = ic.nps_promoter_threshold || 9;
|
|
7687
|
-
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?";
|
|
7688
|
-
while (body.firstChild) body.removeChild(body.firstChild);
|
|
7689
|
-
const heading = document.createElement("div");
|
|
7690
|
-
heading.style.cssText = "font-size: 15px; font-weight: 700; margin-bottom: 10px; text-align: center;";
|
|
7691
|
-
heading.textContent = prompt;
|
|
7692
|
-
body.appendChild(heading);
|
|
7693
|
-
const ta = document.createElement("textarea");
|
|
7694
|
-
const chips = Array.isArray(ic.nps_reason_chips) ? ic.nps_reason_chips : [];
|
|
7695
|
-
if (chips.length) {
|
|
7696
|
-
const chipRow = document.createElement("div");
|
|
7697
|
-
chipRow.style.cssText = "display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin-bottom: 10px;";
|
|
7698
|
-
chips.slice(0, 8).forEach((c) => {
|
|
7699
|
-
const chip = document.createElement("button");
|
|
7700
|
-
chip.type = "button";
|
|
7701
|
-
chip.textContent = c;
|
|
7702
|
-
chip.style.cssText = `padding: 5px 11px; border-radius: 999px; border: 1px solid ${text}33; background: transparent; color: ${text || "inherit"}; font-size: 12px; cursor: pointer;`;
|
|
7703
|
-
chip.addEventListener("click", () => {
|
|
7704
|
-
ta.value = ta.value ? `${ta.value}, ${c}` : c;
|
|
7705
|
-
ta.focus();
|
|
7706
|
-
});
|
|
7707
|
-
chipRow.appendChild(chip);
|
|
7708
|
-
});
|
|
7709
|
-
body.appendChild(chipRow);
|
|
7710
|
-
}
|
|
7711
|
-
ta.className = "aegis-rating-comment";
|
|
7712
|
-
ta.rows = 3;
|
|
7713
|
-
ta.placeholder = "Share a little more…";
|
|
7714
|
-
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;`;
|
|
7715
|
-
body.appendChild(ta);
|
|
7716
|
-
const st = this.inAppStyle(campaign);
|
|
7717
|
-
const row = document.createElement("div");
|
|
7718
|
-
row.style.cssText = "display: flex; gap: 8px; justify-content: center;";
|
|
7719
|
-
let done = false;
|
|
7720
|
-
const finish = (withComment) => {
|
|
7721
|
-
if (done) return;
|
|
7722
|
-
done = true;
|
|
7723
|
-
const comment = withComment ? ta.value.trim() : "";
|
|
7724
|
-
void this.submitWidgetResponse(campaign, "nps", comment ? { score: value, comment } : { score: value });
|
|
7725
|
-
this._renderNpsThankYou(campaign, ic, body, value >= promoterThreshold);
|
|
7726
|
-
};
|
|
7727
|
-
const send = document.createElement("button");
|
|
7728
|
-
send.type = "button";
|
|
7729
|
-
send.textContent = "Send";
|
|
7730
|
-
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;`;
|
|
7731
|
-
send.addEventListener("click", () => finish(true));
|
|
7732
|
-
const skip = document.createElement("button");
|
|
7733
|
-
skip.type = "button";
|
|
7734
|
-
skip.textContent = "Skip";
|
|
7735
|
-
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;`;
|
|
7736
|
-
skip.addEventListener("click", () => finish(false));
|
|
7737
|
-
row.appendChild(send);
|
|
7738
|
-
row.appendChild(skip);
|
|
7739
|
-
body.appendChild(row);
|
|
7740
|
-
ta.focus();
|
|
7741
|
-
}
|
|
7742
|
-
/** NPS thank-you takeover; promoters get an optional review/referral CTA. */
|
|
7743
|
-
_renderNpsThankYou(campaign, ic, body, isPromoter) {
|
|
7744
|
-
while (body.firstChild) body.removeChild(body.firstChild);
|
|
7745
|
-
const modalEl = body.parentElement;
|
|
7746
|
-
if (modalEl) modalEl.style.overflow = "visible";
|
|
7747
|
-
body.style.position = "relative";
|
|
7748
|
-
body.style.minHeight = "176px";
|
|
7749
|
-
body.style.display = "flex";
|
|
7750
|
-
body.style.flexDirection = "column";
|
|
7751
|
-
body.style.alignItems = "center";
|
|
7752
|
-
body.style.justifyContent = "center";
|
|
7753
|
-
const heading = document.createElement("div");
|
|
7754
|
-
heading.style.cssText = "font-size: 16px; font-weight: 700; margin-bottom: 8px; text-align: center;";
|
|
7755
|
-
heading.textContent = ic.thank_you_message || (isPromoter ? "Thanks — glad you love it!" : "Thanks for your feedback!");
|
|
7756
|
-
body.appendChild(heading);
|
|
7757
|
-
const reviewUrl = ic.nps_promoter_url;
|
|
7758
|
-
if (isPromoter && reviewUrl) {
|
|
7759
|
-
const sub = document.createElement("div");
|
|
7760
|
-
sub.style.cssText = "font-size: 13px; opacity: 0.8; margin-bottom: 14px; text-align: center;";
|
|
7761
|
-
sub.textContent = "Would you share that with others?";
|
|
7762
|
-
body.appendChild(sub);
|
|
7763
|
-
const st = this.inAppStyle(campaign);
|
|
7764
|
-
const cta = document.createElement("button");
|
|
7765
|
-
cta.type = "button";
|
|
7766
|
-
cta.textContent = campaign.button_text || "Leave a review";
|
|
7767
|
-
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;`;
|
|
7768
|
-
cta.addEventListener("click", () => {
|
|
7769
|
-
this.trackEvent(campaign.id, "clicked", { meta: { action: "nps_review_redirect" } });
|
|
7770
|
-
const safe = this.sanitizeUrl(reviewUrl);
|
|
7771
|
-
if (safe) this.navigateToCampaignAction(campaign, safe);
|
|
7772
|
-
});
|
|
7773
|
-
body.appendChild(cta);
|
|
7774
|
-
} else {
|
|
7775
|
-
this._playReaction(body, isPromoter ? "affirm" : "acknowledge", void 0, ic);
|
|
7776
|
-
}
|
|
7777
|
-
}
|
|
7778
7542
|
/**
|
|
7779
7543
|
* Evaluate the currently armed campaigns against a client-side event
|
|
7780
7544
|
* and render any that match their `client_trigger`.
|
|
@@ -7836,6 +7600,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7836
7600
|
this._displaySizeMul = this._sizeMul(
|
|
7837
7601
|
campaign.interactive_config
|
|
7838
7602
|
);
|
|
7603
|
+
this._ensureSurveyTemplate(campaign);
|
|
7839
7604
|
const interactiveSubTypes = /* @__PURE__ */ new Set([
|
|
7840
7605
|
"spin_wheel",
|
|
7841
7606
|
"scratch_card",
|
|
@@ -7859,11 +7624,9 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7859
7624
|
}
|
|
7860
7625
|
{
|
|
7861
7626
|
const ic2 = campaign.interactive_config || {};
|
|
7862
|
-
const tpl2 = ic2.card_template;
|
|
7863
|
-
const hasTpl2 = !!tpl2 && typeof tpl2 === "object" && Array.isArray(tpl2.elements) && tpl2.elements.length > 0;
|
|
7864
7627
|
const multiCard2 = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
|
|
7865
7628
|
const overlayCardType = campaign.type === "modal" || campaign.type === "banner" || campaign.type === "full_screen" || campaign.type === "half_interstitial";
|
|
7866
|
-
if (
|
|
7629
|
+
if (this._hasComposedTemplate(ic2) && !multiCard2 && overlayCardType) {
|
|
7867
7630
|
this.renderComposedCardOverlay(campaign, ic2);
|
|
7868
7631
|
this.trackEvent(campaign.id, "impression");
|
|
7869
7632
|
this.emit("campaign-shown", campaign);
|
|
@@ -7985,7 +7748,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
7985
7748
|
sanitizeUrl: (url) => this.sanitizeUrl(url),
|
|
7986
7749
|
navigate: (url, buttonId) => this.navigateToCampaignAction(campaign, url, buttonId),
|
|
7987
7750
|
submitResponse: (responseType, payload) => this.submitWidgetResponse(campaign, responseType, payload),
|
|
7988
|
-
playReaction: (el, intent, lottieFile) => this._playReaction(el, intent, lottieFile, campaign.interactive_config),
|
|
7751
|
+
playReaction: (el, intent, lottieFile, scope) => this._playReaction(el, intent, lottieFile, campaign.interactive_config, scope),
|
|
7989
7752
|
haptic: (pattern) => {
|
|
7990
7753
|
try {
|
|
7991
7754
|
if (typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
|
|
@@ -8010,19 +7773,17 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8010
7773
|
if (campaign.sub_type && renderGame(this.buildRenderContext(campaign), campaign.sub_type)) {
|
|
8011
7774
|
return;
|
|
8012
7775
|
}
|
|
7776
|
+
{
|
|
7777
|
+
const isMultiStepFlow = campaign.sub_type === "quiz" || campaign.sub_type === "multi_step_form";
|
|
7778
|
+
if (this._hasComposedTemplate(ic) && !isMultiStepFlow) {
|
|
7779
|
+
this.renderComposedCardOverlay(campaign, ic);
|
|
7780
|
+
return;
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
8013
7783
|
switch (campaign.sub_type) {
|
|
8014
|
-
case "nps_survey":
|
|
8015
|
-
this.renderNPSSurvey(campaign, ic, bg, text);
|
|
8016
|
-
break;
|
|
8017
7784
|
case "countdown_offer":
|
|
8018
7785
|
this.renderCountdownOffer(campaign, ic, bg, text);
|
|
8019
7786
|
break;
|
|
8020
|
-
case "star_rating":
|
|
8021
|
-
this.renderStarRating(campaign, ic, bg, text);
|
|
8022
|
-
break;
|
|
8023
|
-
case "quick_poll":
|
|
8024
|
-
this.renderQuickPoll(campaign, ic, bg, text);
|
|
8025
|
-
break;
|
|
8026
7787
|
case "quiz":
|
|
8027
7788
|
this.renderQuiz(campaign, ic, bg, text);
|
|
8028
7789
|
break;
|
|
@@ -8189,21 +7950,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8189
7950
|
return true;
|
|
8190
7951
|
}
|
|
8191
7952
|
// --- Interactive Renderers ---
|
|
8192
|
-
renderNPSSurvey(campaign, ic, bg, text) {
|
|
8193
|
-
const overlay = this.createOverlay("aegis-in-app-nps-overlay");
|
|
8194
|
-
const modal = document.createElement("div");
|
|
8195
|
-
modal.style.cssText = `
|
|
8196
|
-
max-width: 360px; width: 90%; border-radius: 16px; overflow: hidden;
|
|
8197
|
-
background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
|
|
8198
|
-
box-shadow: 0 16px 48px rgba(0,0,0,0.22);
|
|
8199
|
-
`;
|
|
8200
|
-
const body = this._buildNPSSurveyBody(campaign, ic, text, "overlay");
|
|
8201
|
-
this.addCloseButton(body, overlay, campaign.id);
|
|
8202
|
-
modal.appendChild(body);
|
|
8203
|
-
overlay.appendChild(modal);
|
|
8204
|
-
this.addAnimationStyles();
|
|
8205
|
-
document.body.appendChild(overlay);
|
|
8206
|
-
}
|
|
8207
7953
|
renderCountdownOffer(campaign, ic, bg, text) {
|
|
8208
7954
|
const overlay = this.createOverlay("aegis-in-app-countdown-overlay");
|
|
8209
7955
|
const modal = document.createElement("div");
|
|
@@ -8291,84 +8037,6 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8291
8037
|
this.addAnimationStyles();
|
|
8292
8038
|
document.body.appendChild(overlay);
|
|
8293
8039
|
}
|
|
8294
|
-
renderStarRating(campaign, ic, bg, text) {
|
|
8295
|
-
const overlay = this.createOverlay("aegis-in-app-rating-overlay");
|
|
8296
|
-
const modal = document.createElement("div");
|
|
8297
|
-
modal.style.cssText = `
|
|
8298
|
-
max-width: 320px; width: 90%; border-radius: 16px; overflow: hidden;
|
|
8299
|
-
background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
|
|
8300
|
-
box-shadow: 0 16px 48px rgba(0,0,0,0.22);
|
|
8301
|
-
`;
|
|
8302
|
-
const body = this._buildStarRatingBody(campaign, ic, text, "overlay");
|
|
8303
|
-
this.addCloseButton(body, overlay, campaign.id);
|
|
8304
|
-
modal.appendChild(body);
|
|
8305
|
-
overlay.appendChild(modal);
|
|
8306
|
-
this.addAnimationStyles();
|
|
8307
|
-
document.body.appendChild(overlay);
|
|
8308
|
-
}
|
|
8309
|
-
renderQuickPoll(campaign, ic, bg, text) {
|
|
8310
|
-
const overlay = this.createOverlay("aegis-in-app-poll-overlay");
|
|
8311
|
-
const modal = document.createElement("div");
|
|
8312
|
-
modal.style.cssText = `
|
|
8313
|
-
max-width: 320px; width: 90%; border-radius: 16px; overflow: hidden;
|
|
8314
|
-
background: ${bg}; color: ${text}; animation: aegisScaleIn 0.3s ease;
|
|
8315
|
-
box-shadow: 0 16px 48px rgba(0,0,0,0.22);
|
|
8316
|
-
`;
|
|
8317
|
-
const body = document.createElement("div");
|
|
8318
|
-
body.style.cssText = "padding: 24px; text-align: center;";
|
|
8319
|
-
const title = document.createElement("div");
|
|
8320
|
-
title.style.cssText = `font-size: 16px; font-weight: 700; margin-bottom: ${campaign.body ? "6px" : "16px"};`;
|
|
8321
|
-
title.textContent = campaign.title || "Quick question";
|
|
8322
|
-
body.appendChild(title);
|
|
8323
|
-
if (campaign.body) {
|
|
8324
|
-
const sub = document.createElement("div");
|
|
8325
|
-
sub.style.cssText = `font-size: 13px; opacity: 0.8; margin-bottom: 16px; color: ${text};`;
|
|
8326
|
-
sub.textContent = campaign.body;
|
|
8327
|
-
body.appendChild(sub);
|
|
8328
|
-
}
|
|
8329
|
-
const options = ic.poll_options || [];
|
|
8330
|
-
const optionsList = document.createElement("div");
|
|
8331
|
-
optionsList.style.cssText = "display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px;";
|
|
8332
|
-
const buttons = [];
|
|
8333
|
-
options.forEach((opt, i) => {
|
|
8334
|
-
const optBtn = document.createElement("button");
|
|
8335
|
-
optBtn.style.cssText = `
|
|
8336
|
-
position: relative; overflow: hidden;
|
|
8337
|
-
padding: 10px 16px; border-radius: 10px; border: 1px solid ${text}33;
|
|
8338
|
-
background: transparent; color: ${text}; font-size: 14px; cursor: pointer;
|
|
8339
|
-
text-align: left; transition: border-color 0.2s ease;
|
|
8340
|
-
`;
|
|
8341
|
-
const lbl = document.createElement("span");
|
|
8342
|
-
lbl.style.cssText = "position: relative; z-index: 1; display: flex; justify-content: space-between; gap: 8px;";
|
|
8343
|
-
const lblText = document.createElement("span");
|
|
8344
|
-
lblText.textContent = opt;
|
|
8345
|
-
const lblPct = document.createElement("span");
|
|
8346
|
-
lblPct.style.cssText = "font-weight: 700; opacity: 0; transition: opacity 0.3s ease;";
|
|
8347
|
-
lbl.appendChild(lblText);
|
|
8348
|
-
lbl.appendChild(lblPct);
|
|
8349
|
-
optBtn.appendChild(lbl);
|
|
8350
|
-
optBtn.addEventListener("click", () => {
|
|
8351
|
-
this.trackEvent(campaign.id, "clicked");
|
|
8352
|
-
buttons.forEach((b) => b.disabled = true);
|
|
8353
|
-
this._paintPollResult(buttons, options, i, null, text);
|
|
8354
|
-
void this.submitWidgetResponse(campaign, "poll", { option_index: i, option_label: opt }).then((resp) => {
|
|
8355
|
-
const dist = this._readPollDistribution(resp, options.length);
|
|
8356
|
-
if (dist) this._paintPollResult(buttons, options, i, dist, text);
|
|
8357
|
-
});
|
|
8358
|
-
const modalEl = body.parentElement;
|
|
8359
|
-
if (modalEl) modalEl.style.overflow = "visible";
|
|
8360
|
-
this._playReaction(body, "affirm", "check.json", ic);
|
|
8361
|
-
});
|
|
8362
|
-
buttons.push(optBtn);
|
|
8363
|
-
optionsList.appendChild(optBtn);
|
|
8364
|
-
});
|
|
8365
|
-
body.appendChild(optionsList);
|
|
8366
|
-
this.addCloseButton(body, overlay, campaign.id);
|
|
8367
|
-
modal.appendChild(body);
|
|
8368
|
-
overlay.appendChild(modal);
|
|
8369
|
-
this.addAnimationStyles();
|
|
8370
|
-
document.body.appendChild(overlay);
|
|
8371
|
-
}
|
|
8372
8040
|
/**
|
|
8373
8041
|
* Multi-step form (SDK Demo v4 W1b) — in-app render type matching the
|
|
8374
8042
|
* Plotline multi-step flow pattern: a progress bar + per-step question with
|
|
@@ -8400,6 +8068,13 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8400
8068
|
const total = steps.length;
|
|
8401
8069
|
let current = 0;
|
|
8402
8070
|
const answers = new Array(total).fill(null);
|
|
8071
|
+
const flow = createFlowAccumulator();
|
|
8072
|
+
const viewedSteps = /* @__PURE__ */ new Set();
|
|
8073
|
+
const fireStepViewed = (idx) => {
|
|
8074
|
+
if (viewedSteps.has(idx)) return;
|
|
8075
|
+
viewedSteps.add(idx);
|
|
8076
|
+
void this.trackEvent(campaign.id, "impression", { stepId: `step_${idx + 1}` });
|
|
8077
|
+
};
|
|
8403
8078
|
const progressTrack = document.createElement("div");
|
|
8404
8079
|
progressTrack.style.cssText = `height: 4px; border-radius: 2px; background: ${text}22; margin-bottom: 16px; overflow: hidden;`;
|
|
8405
8080
|
const progressFill = document.createElement("div");
|
|
@@ -8429,6 +8104,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8429
8104
|
const step = steps[current] || {};
|
|
8430
8105
|
const q = step.question || step.label || `Step ${current + 1}`;
|
|
8431
8106
|
const opts = Array.isArray(step.options) ? step.options : [];
|
|
8107
|
+
fireStepViewed(current);
|
|
8432
8108
|
progressFill.style.width = `${Math.round(current / total * 100)}%`;
|
|
8433
8109
|
stepLabel.textContent = `Step ${current + 1} of ${total}`;
|
|
8434
8110
|
question.textContent = q;
|
|
@@ -8440,6 +8116,7 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8440
8116
|
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;`;
|
|
8441
8117
|
ob.addEventListener("click", () => {
|
|
8442
8118
|
answers[current] = opt;
|
|
8119
|
+
flow.recordStep({ step_id: `step_${current + 1}`, response_type: "form_step", payload: { question: q, answer: opt } });
|
|
8443
8120
|
renderStep();
|
|
8444
8121
|
});
|
|
8445
8122
|
optionsList.appendChild(ob);
|
|
@@ -8463,8 +8140,10 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
8463
8140
|
renderStep();
|
|
8464
8141
|
} else {
|
|
8465
8142
|
progressFill.style.width = "100%";
|
|
8143
|
+
flow.markCompleted();
|
|
8144
|
+
void this.trackEvent(campaign.id, "impression", { stepId: "flow_completed" });
|
|
8466
8145
|
void this.trackEvent(campaign.id, "clicked", { meta: { answers } });
|
|
8467
|
-
void this.submitWidgetResponse(campaign, "form", { answers });
|
|
8146
|
+
void this.submitWidgetResponse(campaign, "form", { answers, flow: flow.consolidated() });
|
|
8468
8147
|
this._playReaction(body, "affirm", "check.json", ic);
|
|
8469
8148
|
window.setTimeout(() => {
|
|
8470
8149
|
if (overlay.parentNode) document.body.removeChild(overlay);
|
|
@@ -10237,17 +9916,18 @@ const _AegisInAppManager = class _AegisInAppManager {
|
|
|
10237
9916
|
*
|
|
10238
9917
|
* Honors prefers-reduced-motion (static badge, no motion).
|
|
10239
9918
|
*/
|
|
10240
|
-
_playReaction(host, intent, lottieFile, ic) {
|
|
9919
|
+
_playReaction(host, intent, lottieFile, ic, scope) {
|
|
10241
9920
|
try {
|
|
10242
9921
|
if (typeof document === "undefined") return;
|
|
10243
9922
|
this.addAnimationStyles();
|
|
10244
9923
|
const reduce = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
10245
|
-
const
|
|
9924
|
+
const viewport = scope === "viewport";
|
|
9925
|
+
const anchor = viewport ? document.body : host || document.body;
|
|
10246
9926
|
const prevPos = anchor.style.position;
|
|
10247
|
-
if (!prevPos || prevPos === "static") anchor.style.position = "relative";
|
|
9927
|
+
if (!viewport && (!prevPos || prevPos === "static")) anchor.style.position = "relative";
|
|
10248
9928
|
const layer = document.createElement("div");
|
|
10249
9929
|
layer.className = "aegis-in-app-reaction";
|
|
10250
|
-
layer.style.cssText = "position: absolute; inset: 0; z-index: 2147483646; pointer-events: none; overflow: visible;";
|
|
9930
|
+
layer.style.cssText = viewport ? "position: fixed; inset: 0; z-index: 2147483646; pointer-events: none; overflow: visible;" : "position: absolute; inset: 0; z-index: 2147483646; pointer-events: none; overflow: visible;";
|
|
10251
9931
|
const SPEC = {
|
|
10252
9932
|
celebrate: { file: "trophy.json", svg: "gift", ring: "#fbbf24", festive: true },
|
|
10253
9933
|
affirm: { file: "affirm.json", svg: "star", ring: "#fbbf24", festive: true },
|