@active-reach/web-sdk 1.28.0 → 1.30.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/index.js CHANGED
@@ -25,6 +25,94 @@ function throttle(func, limit) {
25
25
  }
26
26
  };
27
27
  }
28
+ const PDP_SKU_RE = /\/p\/([^/?#]+)/;
29
+ function parseAction(actionUrl, meta) {
30
+ const url = (actionUrl ?? "").trim();
31
+ const m2 = meta ?? {};
32
+ if (url.startsWith("aegis://")) {
33
+ const rest = url.slice("aegis://".length);
34
+ const [verb, qs] = rest.split("?");
35
+ const params = new URLSearchParams(qs || "");
36
+ switch (verb) {
37
+ case "dismiss":
38
+ return { type: "dismiss" };
39
+ case "request-push":
40
+ case "request_push":
41
+ return { type: "request_push" };
42
+ case "cart-add":
43
+ case "add-to-cart":
44
+ return {
45
+ type: "add_to_cart",
46
+ sku: params.get("sku") || m2.sku || void 0,
47
+ productId: params.get("product_id") || m2.product_id || void 0,
48
+ platformProductId: m2.platform_product_id,
49
+ qty: Number(params.get("qty")) || 1,
50
+ title: m2.title,
51
+ price: m2.price,
52
+ imageUrl: m2.image_url,
53
+ url: m2.platform_url
54
+ };
55
+ case "open-cart":
56
+ case "cart":
57
+ return { type: "open_cart" };
58
+ case "open-login":
59
+ case "login":
60
+ return { type: "open_login" };
61
+ case "open-product":
62
+ case "product": {
63
+ const sku = params.get("sku") || m2.sku || void 0;
64
+ return { type: "open_product", sku, productId: m2.product_id, url: sku ? `/p/${sku}` : void 0 };
65
+ }
66
+ default:
67
+ return { type: "custom", name: verb || "unknown", payload: Object.fromEntries(params) };
68
+ }
69
+ }
70
+ const pdp = PDP_SKU_RE.exec(url);
71
+ if (pdp) {
72
+ return {
73
+ type: "open_product",
74
+ sku: decodeURIComponent(pdp[1]),
75
+ productId: m2.product_id,
76
+ url,
77
+ platformProductId: m2.platform_product_id,
78
+ platformUrl: m2.platform_url
79
+ };
80
+ }
81
+ if (url) {
82
+ return { type: "navigate", url };
83
+ }
84
+ if (m2.sku || m2.product_id) {
85
+ return {
86
+ type: "add_to_cart",
87
+ sku: m2.sku,
88
+ productId: m2.product_id,
89
+ platformProductId: m2.platform_product_id,
90
+ qty: 1,
91
+ title: m2.title,
92
+ price: m2.price,
93
+ imageUrl: m2.image_url,
94
+ url: m2.platform_url
95
+ };
96
+ }
97
+ return { type: "dismiss" };
98
+ }
99
+ function fallbackUrlForAction(a, productUrl = (sku) => `/p/${encodeURIComponent(sku)}`) {
100
+ switch (a.type) {
101
+ case "navigate":
102
+ return a.url;
103
+ case "open_product":
104
+ return a.url || (a.sku ? productUrl(a.sku) : void 0);
105
+ case "add_to_cart":
106
+ return a.url || (a.sku ? productUrl(a.sku) : void 0);
107
+ case "open_cart":
108
+ return "/cart";
109
+ case "open_login":
110
+ return void 0;
111
+ // host-only; no neutral fallback
112
+ default:
113
+ return void 0;
114
+ }
115
+ }
28
116
  const ICONS = {
29
117
  x: [
30
118
  ["path", { d: "M18 6 6 18" }],
@@ -223,12 +311,12 @@ const ICONS = {
223
311
  function escapeAttr(value) {
224
312
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
225
313
  }
226
- const SVG_NS = "http://www.w3.org/2000/svg";
314
+ const SVG_NS$1 = "http://www.w3.org/2000/svg";
227
315
  function lucideSvgEl(name, opts = {}) {
228
316
  const node = ICONS[name];
229
317
  if (!node || typeof document === "undefined") return null;
230
318
  const size = opts.size ?? 24;
231
- const svg = document.createElementNS(SVG_NS, "svg");
319
+ const svg = document.createElementNS(SVG_NS$1, "svg");
232
320
  svg.setAttribute("width", String(size));
233
321
  svg.setAttribute("height", String(size));
234
322
  svg.setAttribute("viewBox", "0 0 24 24");
@@ -245,7 +333,7 @@ function lucideSvgEl(name, opts = {}) {
245
333
  svg.setAttribute("aria-hidden", "true");
246
334
  }
247
335
  for (const [tag, attrs] of node) {
248
- const child = document.createElementNS(SVG_NS, tag);
336
+ const child = document.createElementNS(SVG_NS$1, tag);
249
337
  for (const [k, v] of Object.entries(attrs)) child.setAttribute(k, String(v));
250
338
  svg.appendChild(child);
251
339
  }
@@ -437,6 +525,16 @@ function applyPerCardOverride(tpl, look) {
437
525
  if (look.hide_cta) drop("cta");
438
526
  if (look.hide_rating) drop("rating");
439
527
  if (look.cta_text) elements = elements.map((e) => e.type === "cta" ? { ...e, text: look.cta_text } : e);
528
+ if (look.cta_action || look.cta_url || look.cta_custom_name) {
529
+ elements = elements.map(
530
+ (e) => e.type === "cta" ? {
531
+ ...e,
532
+ ...look.cta_action ? { cta_action: look.cta_action } : {},
533
+ ...look.cta_url ? { cta_url: look.cta_url } : {},
534
+ ...look.cta_custom_name ? { cta_custom_name: look.cta_custom_name } : {}
535
+ } : e
536
+ );
537
+ }
440
538
  if (look.badge) {
441
539
  const badgeEl = { type: "badge", text: look.badge, color: look.badge_color };
442
540
  elements = elements.some((e) => e.type === "badge") ? elements.map((e) => e.type === "badge" ? badgeEl : e) : [elements[0], badgeEl, ...elements.slice(1)].filter(Boolean);
@@ -450,7 +548,7 @@ function priceText(card) {
450
548
  if (card.body && /\d/.test(card.body)) return card.body;
451
549
  return void 0;
452
550
  }
453
- function paintPrice(card, el, accent, ctx) {
551
+ function paintPrice(card, el2, accent, ctx) {
454
552
  const price = priceText(card);
455
553
  if (price === void 0) return null;
456
554
  const meta = card.metadata || {};
@@ -459,10 +557,10 @@ function paintPrice(card, el, accent, ctx) {
459
557
  row.style.cssText = "display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;";
460
558
  const p = document.createElement("span");
461
559
  p.textContent = price;
462
- if (el.variant === "plain") {
560
+ if (el2.variant === "plain") {
463
561
  p.style.cssText = `color:${accent};font-weight:700;font-size:13px;`;
464
562
  } else {
465
- const pillBg = el.color ? ctx.sanitizeColor(el.color) : "#059669";
563
+ const pillBg = el2.color ? ctx.sanitizeColor(el2.color) : "#059669";
466
564
  p.style.cssText = `background:${pillBg};color:#fff;font-weight:800;font-size:12px;padding:2px 7px;border-radius:6px;box-shadow:0 2px 6px rgba(0,0,0,0.30);`;
467
565
  }
468
566
  row.appendChild(p);
@@ -514,14 +612,14 @@ function contrastFg(bg) {
514
612
  const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
515
613
  return lum < 0.5 ? "#ffffff" : "#1f2937";
516
614
  }
517
- function buildCountdownElement(el, fg, muted) {
518
- const targetStr = typeof el.countdown_target === "string" ? el.countdown_target : "";
615
+ function buildCountdownElement(el2, fg, muted) {
616
+ const targetStr = typeof el2.countdown_target === "string" ? el2.countdown_target : "";
519
617
  const target = targetStr ? Date.parse(targetStr) : NaN;
520
618
  if (!targetStr || isNaN(target)) return null;
521
619
  const wrap = document.createElement("div");
522
620
  wrap.style.cssText = "display:flex;align-items:baseline;gap:8px;font-variant-numeric:tabular-nums;margin:2px 0;";
523
621
  const label = document.createElement("span");
524
- label.textContent = typeof el.countdown_label === "string" && el.countdown_label || "Ends in";
622
+ label.textContent = typeof el2.countdown_label === "string" && el2.countdown_label || "Ends in";
525
623
  label.style.cssText = `font-size:12px;color:${muted};`;
526
624
  const time = document.createElement("span");
527
625
  time.style.cssText = `font-size:15px;font-weight:700;color:${fg};letter-spacing:0.2px;`;
@@ -543,13 +641,13 @@ function buildCountdownElement(el, fg, muted) {
543
641
  timer = setInterval(tick, 1e3);
544
642
  return wrap;
545
643
  }
546
- function appendRatingThanks(wrap, el, ctx, fg, accent, isHigh) {
547
- appendThanks(wrap, el, fg, "Thanks for rating us!");
548
- const url = isHigh && typeof el.rating_high_url === "string" ? ctx.sanitizeUrl(el.rating_high_url) : "";
644
+ function appendRatingThanks(wrap, el2, ctx, fg, accent, isHigh) {
645
+ appendThanks(wrap, el2, fg, "Thanks for rating us!");
646
+ const url = isHigh && typeof el2.rating_high_url === "string" ? ctx.sanitizeUrl(el2.rating_high_url) : "";
549
647
  if (url) {
550
648
  const cta = document.createElement("button");
551
649
  cta.type = "button";
552
- cta.textContent = el.rating_high_cta || "Leave a review";
650
+ cta.textContent = el2.rating_high_cta || "Leave a review";
553
651
  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;`;
554
652
  cta.addEventListener("click", () => {
555
653
  ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "rating_input", cta: "high_review" } });
@@ -559,10 +657,10 @@ function appendRatingThanks(wrap, el, ctx, fg, accent, isHigh) {
559
657
  wrap.appendChild(cta);
560
658
  }
561
659
  }
562
- function renderRatingFollowupStep(wrap, el, ctx, stars, fg, accent) {
660
+ function renderRatingFollowupStep(wrap, el2, ctx, stars2, fg, accent) {
563
661
  while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
564
662
  const heading = document.createElement("div");
565
- heading.textContent = el.rating_followup_prompt || "Sorry to hear that — what went wrong?";
663
+ heading.textContent = el2.rating_followup_prompt || "Sorry to hear that — what went wrong?";
566
664
  heading.style.cssText = `font-size:13px;font-weight:700;color:${fg};text-align:center;margin-bottom:8px;`;
567
665
  wrap.appendChild(heading);
568
666
  const ta = document.createElement("textarea");
@@ -576,9 +674,9 @@ function renderRatingFollowupStep(wrap, el, ctx, stars, fg, accent) {
576
674
  if (done) return;
577
675
  done = true;
578
676
  const comment = withComment ? ta.value.trim() : "";
579
- void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "rating", comment ? { stars, comment } : { stars }));
677
+ void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "rating", comment ? { stars: stars2, comment } : { stars: stars2 }));
580
678
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, "acknowledge");
581
- appendThanks(wrap, el, fg, "Thanks — we’ll do better.");
679
+ appendThanks(wrap, el2, fg, "Thanks — we’ll do better.");
582
680
  };
583
681
  const btnRow = document.createElement("div");
584
682
  btnRow.style.cssText = "display:flex;gap:8px;justify-content:center;";
@@ -597,23 +695,23 @@ function renderRatingFollowupStep(wrap, el, ctx, stars, fg, accent) {
597
695
  wrap.appendChild(btnRow);
598
696
  ta.focus();
599
697
  }
600
- function buildRatingInputElement(el, ctx, fg) {
698
+ function buildRatingInputElement(el2, ctx, fg) {
601
699
  const accent = ctx.sanitizeColor(ctx.campaign.background_color || "#4169e1");
602
700
  const wrap = document.createElement("div");
603
701
  wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
604
- if (el.content) {
702
+ if (el2.content) {
605
703
  const q = document.createElement("div");
606
- q.textContent = el.content;
704
+ q.textContent = el2.content;
607
705
  q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
608
706
  wrap.appendChild(q);
609
707
  }
610
- const scale = typeof el.rating_scale === "number" && el.rating_scale >= 2 && el.rating_scale <= 10 ? Math.floor(el.rating_scale) : 5;
708
+ const scale = typeof el2.rating_scale === "number" && el2.rating_scale >= 2 && el2.rating_scale <= 10 ? Math.floor(el2.rating_scale) : 5;
611
709
  const row = document.createElement("div");
612
710
  row.style.cssText = "display:flex;gap:6px;";
613
- const stars = [];
711
+ const stars2 = [];
614
712
  let submitted = false;
615
713
  const paint = (upto) => {
616
- stars.forEach((s, idx) => {
714
+ stars2.forEach((s, idx) => {
617
715
  const on = idx < upto;
618
716
  s.setAttribute("stroke", on ? "#f59e0b" : "#d1d5db");
619
717
  s.setAttribute("fill", on ? "#f59e0b" : "none");
@@ -627,7 +725,7 @@ function buildRatingInputElement(el, ctx, fg) {
627
725
  const svg = lucideSvgEl("star", { size: 26, color: "#d1d5db" });
628
726
  if (svg) {
629
727
  btn.appendChild(svg);
630
- stars.push(svg);
728
+ stars2.push(svg);
631
729
  }
632
730
  btn.addEventListener("mouseenter", () => {
633
731
  if (!submitted) paint(i);
@@ -641,13 +739,13 @@ function buildRatingInputElement(el, ctx, fg) {
641
739
  submitted = true;
642
740
  paint(i);
643
741
  ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "rating_input", stars: i } });
644
- const lowThreshold = typeof el.rating_followup_threshold === "number" ? el.rating_followup_threshold : Math.ceil(scale * 0.8);
645
- if (el.rating_followup_enabled === true && i <= lowThreshold) {
646
- renderRatingFollowupStep(wrap, el, ctx, i, fg, accent);
742
+ const lowThreshold = typeof el2.rating_followup_threshold === "number" ? el2.rating_followup_threshold : Math.ceil(scale * 0.8);
743
+ if (el2.rating_followup_enabled === true && i <= lowThreshold) {
744
+ renderRatingFollowupStep(wrap, el2, ctx, i, fg, accent);
647
745
  } else {
648
746
  void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "rating", { stars: i }));
649
747
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, i >= Math.ceil(scale * 0.6) ? "affirm" : "acknowledge");
650
- appendRatingThanks(wrap, el, ctx, fg, accent, i >= Math.ceil(scale * 0.6));
748
+ appendRatingThanks(wrap, el2, ctx, fg, accent, i >= Math.ceil(scale * 0.6));
651
749
  }
652
750
  });
653
751
  row.appendChild(btn);
@@ -655,8 +753,8 @@ function buildRatingInputElement(el, ctx, fg) {
655
753
  wrap.appendChild(row);
656
754
  return wrap;
657
755
  }
658
- function appendThanks(wrap, el, fg, fallback) {
659
- const msg = typeof el.thanks_text === "string" && el.thanks_text.trim() ? el.thanks_text : fallback;
756
+ function appendThanks(wrap, el2, fg, fallback) {
757
+ const msg = typeof el2.thanks_text === "string" && el2.thanks_text.trim() ? el2.thanks_text : fallback;
660
758
  if (!msg) return;
661
759
  const note = document.createElement("div");
662
760
  note.textContent = msg;
@@ -664,14 +762,14 @@ function appendThanks(wrap, el, fg, fallback) {
664
762
  note.style.cssText = `font-size:12px;font-weight:600;color:${fg};opacity:0.85;margin-top:4px;`;
665
763
  wrap.appendChild(note);
666
764
  }
667
- function buildPollElement(el, ctx, fg, accent) {
668
- const opts = Array.isArray(el.poll_options) ? el.poll_options.filter((o) => typeof o === "string" && o.trim().length > 0) : [];
765
+ function buildPollElement(el2, ctx, fg, accent) {
766
+ const opts = Array.isArray(el2.poll_options) ? el2.poll_options.filter((o) => typeof o === "string" && o.trim().length > 0) : [];
669
767
  if (opts.length < 2) return null;
670
768
  const wrap = document.createElement("div");
671
769
  wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
672
- if (el.content) {
770
+ if (el2.content) {
673
771
  const q = document.createElement("div");
674
- q.textContent = el.content;
772
+ q.textContent = el2.content;
675
773
  q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
676
774
  wrap.appendChild(q);
677
775
  }
@@ -701,24 +799,24 @@ function buildPollElement(el, ctx, fg, accent) {
701
799
  ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "poll", option_index: idx, option_label: label } });
702
800
  void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "poll", { option_index: idx, option_label: label }));
703
801
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, "affirm");
704
- appendThanks(wrap, el, fg, "Thanks for your answer!");
802
+ appendThanks(wrap, el2, fg, "Thanks for your answer!");
705
803
  });
706
804
  wrap.appendChild(btn);
707
805
  });
708
806
  return wrap;
709
807
  }
710
- function appendNpsThanks(wrap, el, ctx, fg, accent, isPromoter) {
808
+ function appendNpsThanks(wrap, el2, ctx, fg, accent, isPromoter) {
711
809
  while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
712
810
  const note = document.createElement("div");
713
811
  note.setAttribute("role", "status");
714
- 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.");
812
+ note.textContent = (typeof el2.thanks_text === "string" && el2.thanks_text.trim() ? el2.thanks_text : "") || (isPromoter ? "Thank you! We’d love a public review." : "Thanks — your feedback helps us improve.");
715
813
  note.style.cssText = `font-size:13px;font-weight:700;color:${fg};text-align:center;margin:6px 0;`;
716
814
  wrap.appendChild(note);
717
- const url = isPromoter && typeof el.nps_promoter_url === "string" ? ctx.sanitizeUrl(el.nps_promoter_url) : "";
815
+ const url = isPromoter && typeof el2.nps_promoter_url === "string" ? ctx.sanitizeUrl(el2.nps_promoter_url) : "";
718
816
  if (url) {
719
817
  const cta = document.createElement("button");
720
818
  cta.type = "button";
721
- cta.textContent = el.nps_promoter_cta || "Leave a review";
819
+ cta.textContent = el2.nps_promoter_cta || "Leave a review";
722
820
  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;`;
723
821
  cta.addEventListener("click", () => {
724
822
  ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "nps", cta: "promoter_review" } });
@@ -728,30 +826,30 @@ function appendNpsThanks(wrap, el, ctx, fg, accent, isPromoter) {
728
826
  wrap.appendChild(cta);
729
827
  }
730
828
  }
731
- function renderNpsReasonStep(wrap, el, ctx, score, fg, accent) {
732
- const promoterThreshold = typeof el.nps_promoter_threshold === "number" ? el.nps_promoter_threshold : 9;
829
+ function renderNpsReasonStep(wrap, el2, ctx, score, fg, accent) {
830
+ const promoterThreshold = typeof el2.nps_promoter_threshold === "number" ? el2.nps_promoter_threshold : 9;
733
831
  const band = npsBandFromScore(score);
734
- 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?";
832
+ const prompt = score >= promoterThreshold ? el2.nps_follow_up_promoter || "What do you love most about us?" : score <= 6 ? el2.nps_follow_up_detractor || "How can we improve?" : el2.nps_follow_up_passive || "What would make us a 10?";
735
833
  while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
736
834
  const heading = document.createElement("div");
737
835
  heading.textContent = prompt;
738
836
  heading.style.cssText = `font-size:13px;font-weight:700;color:${fg};text-align:center;margin-bottom:8px;`;
739
837
  wrap.appendChild(heading);
740
838
  const ta = document.createElement("textarea");
741
- const chips = Array.isArray(el.nps_reason_chips) ? el.nps_reason_chips : [];
839
+ const chips = Array.isArray(el2.nps_reason_chips) ? el2.nps_reason_chips : [];
742
840
  if (chips.length) {
743
841
  const chipRow = document.createElement("div");
744
842
  chipRow.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-bottom:8px;";
745
843
  chips.slice(0, 8).forEach((c) => {
746
- const chip = document.createElement("button");
747
- chip.type = "button";
748
- chip.textContent = c;
749
- 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;`;
750
- chip.addEventListener("click", () => {
844
+ const chip2 = document.createElement("button");
845
+ chip2.type = "button";
846
+ chip2.textContent = c;
847
+ chip2.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;`;
848
+ chip2.addEventListener("click", () => {
751
849
  ta.value = ta.value ? `${ta.value}, ${c}` : c;
752
850
  ta.focus();
753
851
  });
754
- chipRow.appendChild(chip);
852
+ chipRow.appendChild(chip2);
755
853
  });
756
854
  wrap.appendChild(chipRow);
757
855
  }
@@ -767,7 +865,7 @@ function renderNpsReasonStep(wrap, el, ctx, score, fg, accent) {
767
865
  const comment = withComment ? ta.value.trim() : "";
768
866
  void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "nps", comment ? { score, band, comment } : { score, band }));
769
867
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, score >= promoterThreshold ? "affirm" : score <= 6 ? "empathize" : "acknowledge");
770
- appendNpsThanks(wrap, el, ctx, fg, accent, score >= promoterThreshold);
868
+ appendNpsThanks(wrap, el2, ctx, fg, accent, score >= promoterThreshold);
771
869
  };
772
870
  const btnRow = document.createElement("div");
773
871
  btnRow.style.cssText = "display:flex;gap:8px;justify-content:center;";
@@ -786,12 +884,12 @@ function renderNpsReasonStep(wrap, el, ctx, score, fg, accent) {
786
884
  wrap.appendChild(btnRow);
787
885
  ta.focus();
788
886
  }
789
- function buildNpsElement(el, ctx, fg, accent, muted) {
887
+ function buildNpsElement(el2, ctx, fg, accent, muted) {
790
888
  const wrap = document.createElement("div");
791
889
  wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
792
- if (el.content) {
890
+ if (el2.content) {
793
891
  const q = document.createElement("div");
794
- q.textContent = el.content;
892
+ q.textContent = el2.content;
795
893
  q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
796
894
  wrap.appendChild(q);
797
895
  }
@@ -823,12 +921,12 @@ function buildNpsElement(el, ctx, fg, accent, muted) {
823
921
  btn.style.opacity = "1";
824
922
  const npsPayload = { score: n, band: npsBandFromScore(n) };
825
923
  ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "nps", score: n, band: npsPayload.band } });
826
- if (el.nps_followup_enabled === true) {
827
- renderNpsReasonStep(wrap, el, ctx, n, fg, accent);
924
+ if (el2.nps_followup_enabled === true) {
925
+ renderNpsReasonStep(wrap, el2, ctx, n, fg, accent);
828
926
  } else {
829
927
  void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "nps", { ...npsPayload }));
830
928
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, n >= 9 ? "affirm" : n >= 7 ? "acknowledge" : "empathize");
831
- appendThanks(wrap, el, fg, "Thanks for your feedback!");
929
+ appendThanks(wrap, el2, fg, "Thanks for your feedback!");
832
930
  }
833
931
  });
834
932
  row.appendChild(btn);
@@ -845,22 +943,22 @@ function buildNpsElement(el, ctx, fg, accent, muted) {
845
943
  wrap.appendChild(scale);
846
944
  return wrap;
847
945
  }
848
- function buildProgressElement(el, ctx, accent, muted) {
946
+ function buildProgressElement(el2, ctx, accent, muted) {
849
947
  const { trackEvent, campaign, sanitizeColor } = ctx;
850
- const goal = el.goal_type || "cart_total";
948
+ const goal = el2.goal_type || "cart_total";
851
949
  const moneyGoal = goal === "cart_total";
852
- const fill = el.fill ? sanitizeColor(el.fill) : accent;
950
+ const fill = el2.fill ? sanitizeColor(el2.fill) : accent;
853
951
  const props = () => ctx.properties && ctx.properties() || {};
854
952
  const sym = () => {
855
953
  var _a;
856
- if (el.currency_symbol) return el.currency_symbol;
954
+ if (el2.currency_symbol) return el2.currency_symbol;
857
955
  const code = ctx.getCartState && ((_a = ctx.getCartState()) == null ? void 0 : _a.currency);
858
956
  const M = { INR: "₹", USD: "$", EUR: "€", GBP: "£" };
859
957
  return code && M[code] || "";
860
958
  };
861
959
  const threshold = () => {
862
- if (typeof el.threshold === "number" && el.threshold > 0) return el.threshold;
863
- if (el.threshold_source === "free_delivery_setting") {
960
+ if (typeof el2.threshold === "number" && el2.threshold > 0) return el2.threshold;
961
+ if (el2.threshold_source === "free_delivery_setting") {
864
962
  const v = Number(props()["cart.free_delivery_above"]);
865
963
  if (v > 0) return v;
866
964
  }
@@ -901,9 +999,9 @@ function buildProgressElement(el, ctx, accent, muted) {
901
999
  goalType: goal,
902
1000
  threshold: th,
903
1001
  current: cur,
904
- rewardText: el.reward_text,
1002
+ rewardText: el2.reward_text,
905
1003
  currencySymbol: moneyGoal ? sym() : "",
906
- showPct: el.show_pct
1004
+ showPct: el2.show_pct
907
1005
  });
908
1006
  sub.textContent = labeled.text;
909
1007
  if (complete) {
@@ -912,8 +1010,8 @@ function buildProgressElement(el, ctx, accent, muted) {
912
1010
  if (!firedUnlock) {
913
1011
  firedUnlock = true;
914
1012
  if (seenIncomplete) {
915
- trackEvent(campaign.id, "clicked", { stepId: el.id || "unlocked" });
916
- if (el.celebrate_on_complete !== false) (_a = ctx.playReaction) == null ? void 0 : _a.call(ctx, wrap, "celebrate");
1013
+ trackEvent(campaign.id, "clicked", { stepId: el2.id || "unlocked" });
1014
+ if (el2.celebrate_on_complete !== false) (_a = ctx.playReaction) == null ? void 0 : _a.call(ctx, wrap, "celebrate");
917
1015
  }
918
1016
  }
919
1017
  } else {
@@ -931,6 +1029,40 @@ function buildProgressElement(el, ctx, accent, muted) {
931
1029
  }
932
1030
  return wrap;
933
1031
  }
1032
+ function buildCtaAction(el2, card, meta, inline, sanitizeUrl) {
1033
+ const kind = el2.cta_action ?? (inline ? "add_to_cart" : "navigate");
1034
+ const safe = (u) => u ? sanitizeUrl(u) || void 0 : void 0;
1035
+ const url = safe(el2.cta_url || card.cta_url);
1036
+ const productId = meta.product_id;
1037
+ const sku = meta.sku;
1038
+ const platformProductId = meta.platform_product_id;
1039
+ const platformUrl = safe(meta.platform_url);
1040
+ switch (kind) {
1041
+ case "open_cart":
1042
+ return { type: "open_cart" };
1043
+ case "open_login":
1044
+ return { type: "open_login" };
1045
+ case "open_product":
1046
+ return { type: "open_product", productId, sku, url: platformUrl || url, platformProductId, platformUrl };
1047
+ case "navigate":
1048
+ return { type: "navigate", url: url || "" };
1049
+ case "custom":
1050
+ return { type: "custom", name: el2.cta_custom_name || "cta", payload: { ...meta, url } };
1051
+ case "add_to_cart":
1052
+ default:
1053
+ return {
1054
+ type: "add_to_cart",
1055
+ productId,
1056
+ sku,
1057
+ platformProductId,
1058
+ title: card.title,
1059
+ price: meta.price ?? card.body,
1060
+ imageUrl: card.image_url,
1061
+ url: platformUrl || url,
1062
+ qty: 1
1063
+ };
1064
+ }
1065
+ }
934
1066
  function renderCardFromTemplate(card, opts) {
935
1067
  const { ctx, action, layout, layoutAccent } = opts;
936
1068
  const inline = action === "cart";
@@ -943,13 +1075,14 @@ function renderCardFromTemplate(card, opts) {
943
1075
  const fg = contrastFg(frame.bg ? sanitizeColor(frame.bg) : "#ffffff");
944
1076
  const muted = fg === "#ffffff" ? "rgba(255,255,255,0.72)" : "#6b7280";
945
1077
  const tile = document.createElement("div");
1078
+ tile.setAttribute("data-aegis-reaction-anchor", "");
946
1079
  const cardFont = resolveCardFont(frame.fontFamily);
947
1080
  ensureCardFontLoaded(cardFont);
948
1081
  const radius = frame.radius !== void 0 ? `${parseInt(String(frame.radius), 10) || 12}px` : "12px";
949
1082
  const fLayout = frame.layout;
950
1083
  const dir = fLayout === "row" ? "row" : "column";
951
1084
  const tileGap = frame.gap != null ? frame.gap : 6;
952
- const sizing = fLayout ? frame.width != null ? `width:${typeof frame.width === "number" ? `${frame.width}px` : frame.width};` : "flex:1 1 auto; width:100%;" : layout === "grid" ? "" : "flex:0 0 150px; scroll-snap-align:start;";
1085
+ const sizing = fLayout ? frame.width != null ? `width:${typeof frame.width === "number" ? `${frame.width}px` : frame.width};` : "flex:1 1 auto; width:100%;" : layout === "grid" || layout === "list" ? "" : "flex:0 0 150px; scroll-snap-align:start;";
953
1086
  tile.style.cssText = `
954
1087
  box-sizing:border-box; display:flex; flex-direction:${dir}; ${fLayout === "row" ? "align-items:center;" : ""} gap:${tileGap}px; position:relative;
955
1088
  color:${fg};
@@ -966,11 +1099,11 @@ function renderCardFromTemplate(card, opts) {
966
1099
  const elements = tpl.elements && tpl.elements.length ? tpl.elements : DEFAULT_CARD_TEMPLATE.elements;
967
1100
  let imageWrap = null;
968
1101
  const paintInto = (container, els) => {
969
- for (const el of els) {
970
- switch (el.type) {
1102
+ for (const el2 of els) {
1103
+ switch (el2.type) {
971
1104
  case "image": {
972
1105
  const wrap = document.createElement("div");
973
- wrap.style.cssText = `position:relative;width:100%;aspect-ratio:${ASPECT$2[el.aspect || "1/1"] || "1 / 1"};border-radius:${el.radius ?? 10}px;overflow:hidden;background:#f7f8fa;`;
1106
+ wrap.style.cssText = `position:relative;width:100%;aspect-ratio:${ASPECT$2[el2.aspect || "1/1"] || "1 / 1"};border-radius:${el2.radius ?? 10}px;overflow:hidden;background:#f7f8fa;`;
974
1107
  const safeVideo = card.video_url ? sanitizeUrl(card.video_url) : "";
975
1108
  if (safeVideo) {
976
1109
  const v = document.createElement("video");
@@ -998,17 +1131,17 @@ function renderCardFromTemplate(card, opts) {
998
1131
  break;
999
1132
  }
1000
1133
  case "badge": {
1001
- const text = el.text;
1134
+ const text = el2.text;
1002
1135
  if (!text) break;
1003
1136
  const badge = document.createElement("span");
1004
1137
  badge.textContent = text;
1005
- const bg = el.color ? sanitizeColor(el.color) : brand;
1138
+ const bg = el2.color ? sanitizeColor(el2.color) : brand;
1006
1139
  badge.style.cssText = `position:absolute;top:6px;left:6px;z-index:1;background:${bg};color:#fff;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:0.03em;padding:3px 6px;border-radius:5px;`;
1007
1140
  (imageWrap || container).appendChild(badge);
1008
1141
  break;
1009
1142
  }
1010
1143
  case "price": {
1011
- const node = paintPrice(card, el, accent, ctx);
1144
+ const node = paintPrice(card, el2, accent, ctx);
1012
1145
  if (node) container.appendChild(node);
1013
1146
  break;
1014
1147
  }
@@ -1016,8 +1149,8 @@ function renderCardFromTemplate(card, opts) {
1016
1149
  if (!card.title) break;
1017
1150
  const t = document.createElement("div");
1018
1151
  t.textContent = card.title;
1019
- const lines = el.lines && el.lines > 0 ? el.lines : 2;
1020
- t.style.cssText = `font-size:12.5px;font-weight:600;line-height:1.3;text-align:${el.align ?? "left"};color:${el.color ? sanitizeColor(el.color) : fg};display:-webkit-box;-webkit-line-clamp:${lines};-webkit-box-orient:vertical;overflow:hidden;`;
1152
+ const lines = el2.lines && el2.lines > 0 ? el2.lines : 2;
1153
+ t.style.cssText = `font-size:12.5px;font-weight:600;line-height:1.3;text-align:${el2.align ?? "left"};color:${el2.color ? sanitizeColor(el2.color) : fg};display:-webkit-box;-webkit-line-clamp:${lines};-webkit-box-orient:vertical;overflow:hidden;`;
1021
1154
  container.appendChild(t);
1022
1155
  break;
1023
1156
  }
@@ -1025,8 +1158,8 @@ function renderCardFromTemplate(card, opts) {
1025
1158
  if (!card.body) break;
1026
1159
  const s = document.createElement("div");
1027
1160
  s.textContent = card.body;
1028
- const lines = el.lines && el.lines > 0 ? el.lines : 2;
1029
- s.style.cssText = `font-size:11px;color:${el.color ? sanitizeColor(el.color) : muted};line-height:1.3;text-align:${el.align ?? "left"};display:-webkit-box;-webkit-line-clamp:${lines};-webkit-box-orient:vertical;overflow:hidden;`;
1161
+ const lines = el2.lines && el2.lines > 0 ? el2.lines : 2;
1162
+ s.style.cssText = `font-size:11px;color:${el2.color ? sanitizeColor(el2.color) : muted};line-height:1.3;text-align:${el2.align ?? "left"};display:-webkit-box;-webkit-line-clamp:${lines};-webkit-box-orient:vertical;overflow:hidden;`;
1030
1163
  container.appendChild(s);
1031
1164
  break;
1032
1165
  }
@@ -1038,25 +1171,25 @@ function renderCardFromTemplate(card, opts) {
1038
1171
  case "category": {
1039
1172
  const cat = meta.category;
1040
1173
  if (typeof cat !== "string" || !cat) break;
1041
- const chip = document.createElement("span");
1042
- chip.textContent = cat;
1043
- chip.style.cssText = "align-self:flex-start;background:#f3f4f6;color:#6b7280;font-size:10px;font-weight:500;padding:2px 7px;border-radius:6px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";
1044
- container.appendChild(chip);
1174
+ const chip2 = document.createElement("span");
1175
+ chip2.textContent = cat;
1176
+ chip2.style.cssText = "align-self:flex-start;background:#f3f4f6;color:#6b7280;font-size:10px;font-weight:500;padding:2px 7px;border-radius:6px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";
1177
+ container.appendChild(chip2);
1045
1178
  break;
1046
1179
  }
1047
1180
  // ── rich-card-editor elements (P0) ──────────────────────────────────
1048
1181
  case "icon": {
1049
- const px = ICON_PX[el.size || "md"] || 30;
1182
+ const px = ICON_PX[el2.size || "md"] || 30;
1050
1183
  const node = document.createElement("div");
1051
- const iconColor = el.color ? sanitizeColor(el.color) : fg;
1052
- if (el.bubble) {
1053
- const bubbleTint = el.color ? sanitizeColor(el.color) : brand;
1184
+ const iconColor = el2.color ? sanitizeColor(el2.color) : fg;
1185
+ if (el2.bubble) {
1186
+ const bubbleTint = el2.color ? sanitizeColor(el2.color) : brand;
1054
1187
  node.style.cssText = `flex:0 0 auto;width:${px}px;height:${px}px;border-radius:999px;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb, ${bubbleTint} 16%, transparent);`;
1055
1188
  } else {
1056
1189
  node.style.cssText = "flex:0 0 auto;display:flex;align-items:center;";
1057
1190
  }
1058
- const svg = lucideSvgEl(el.icon_name || "circle-check", {
1059
- size: el.bubble ? Math.round(px * 0.55) : Math.round(px * 0.7),
1191
+ const svg = lucideSvgEl(el2.icon_name || "circle-check", {
1192
+ size: el2.bubble ? Math.round(px * 0.55) : Math.round(px * 0.7),
1060
1193
  color: iconColor,
1061
1194
  strokeWidth: 2
1062
1195
  });
@@ -1066,84 +1199,64 @@ function renderCardFromTemplate(card, opts) {
1066
1199
  }
1067
1200
  case "text": {
1068
1201
  const t = document.createElement("div");
1069
- const fs = TEXT_PX[el.size || "md"] || 13;
1070
- t.style.cssText = `font-size:${fs}px;font-weight:${WEIGHT[el.weight ?? "medium"]};text-align:${el.align ?? "left"};color:${el.color ? sanitizeColor(el.color) : fg};line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;`;
1071
- t.textContent = interpolateTokens(el.content ?? "", ctx);
1202
+ const fs = TEXT_PX[el2.size || "md"] || 13;
1203
+ t.style.cssText = `font-size:${fs}px;font-weight:${WEIGHT[el2.weight ?? "medium"]};text-align:${el2.align ?? "left"};color:${el2.color ? sanitizeColor(el2.color) : fg};line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;`;
1204
+ t.textContent = interpolateTokens(el2.content ?? "", ctx);
1072
1205
  container.appendChild(t);
1073
1206
  break;
1074
1207
  }
1075
1208
  case "spacer": {
1076
1209
  const sp = document.createElement("div");
1077
- sp.style.height = `${el.height ?? 8}px`;
1210
+ sp.style.height = `${el2.height ?? 8}px`;
1078
1211
  container.appendChild(sp);
1079
1212
  break;
1080
1213
  }
1081
1214
  case "progress": {
1082
- container.appendChild(buildProgressElement(el, ctx, brand, muted));
1215
+ container.appendChild(buildProgressElement(el2, ctx, brand, muted));
1083
1216
  break;
1084
1217
  }
1085
1218
  case "countdown": {
1086
- const cd = buildCountdownElement(el, fg, muted);
1219
+ const cd = buildCountdownElement(el2, fg, muted);
1087
1220
  if (cd) container.appendChild(cd);
1088
1221
  break;
1089
1222
  }
1090
1223
  case "rating_input": {
1091
- container.appendChild(buildRatingInputElement(el, ctx, fg));
1224
+ container.appendChild(buildRatingInputElement(el2, ctx, fg));
1092
1225
  break;
1093
1226
  }
1094
1227
  case "poll": {
1095
- const p = buildPollElement(el, ctx, fg, brand);
1228
+ const p = buildPollElement(el2, ctx, fg, brand);
1096
1229
  if (p) container.appendChild(p);
1097
1230
  break;
1098
1231
  }
1099
1232
  case "nps": {
1100
- container.appendChild(buildNpsElement(el, ctx, fg, brand, muted));
1233
+ container.appendChild(buildNpsElement(el2, ctx, fg, brand, muted));
1101
1234
  break;
1102
1235
  }
1103
1236
  case "group": {
1104
1237
  const g = document.createElement("div");
1105
- const gdir = el.layout === "row" ? "row" : "column";
1106
- g.style.cssText = `display:flex;flex-direction:${gdir};${el.layout === "row" ? "align-items:center;" : ""}gap:${el.gap ?? 4}px;min-width:0;${el.layout === "row" ? "" : "flex:1 1 auto;"}`;
1107
- paintInto(g, el.elements || []);
1238
+ const gdir = el2.layout === "row" ? "row" : "column";
1239
+ g.style.cssText = `display:flex;flex-direction:${gdir};${el2.layout === "row" ? "align-items:center;" : ""}gap:${el2.gap ?? 4}px;min-width:0;${el2.layout === "row" ? "" : "flex:1 1 auto;"}`;
1240
+ paintInto(g, el2.elements || []);
1108
1241
  container.appendChild(g);
1109
1242
  break;
1110
1243
  }
1111
1244
  case "cta": {
1112
- const label = el.text || card.cta_text || (inline ? "Add" : "Shop");
1245
+ const label = el2.text || card.cta_text || (inline ? "Add" : "Shop");
1113
1246
  const cta = document.createElement("button");
1114
1247
  cta.className = "aegis-cta";
1115
1248
  cta.textContent = label;
1116
- const overlayOnImage = !!el.overlay && !!imageWrap;
1249
+ const overlayOnImage = !!el2.overlay && !!imageWrap;
1117
1250
  if (overlayOnImage) {
1118
- cta.style.cssText = el.variant === "outline" ? `position:absolute;right:6px;bottom:6px;background:#fff;color:${brand};border:1px solid ${brand};border-radius:9px;padding:5px 18px;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:0.04em;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.14);` : `position:absolute;right:6px;bottom:6px;background:${brand};color:#fff;border:none;border-radius:9px;padding:5px 18px;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:0.04em;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.14);`;
1251
+ cta.style.cssText = el2.variant === "outline" ? `position:absolute;right:6px;bottom:6px;background:#fff;color:${brand};border:1px solid ${brand};border-radius:9px;padding:5px 18px;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:0.04em;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.14);` : `position:absolute;right:6px;bottom:6px;background:${brand};color:#fff;border:none;border-radius:9px;padding:5px 18px;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:0.04em;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,0.14);`;
1119
1252
  } else {
1120
- cta.style.cssText = el.variant === "outline" ? `margin-top:${fLayout === "row" ? "0" : "auto"};background:transparent;color:${brand};border:1px solid ${brand};border-radius:999px;padding:6px 10px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;` : `margin-top:${fLayout === "row" ? "0" : "auto"};background:${brand};color:#fff;border:none;border-radius:999px;padding:7px 10px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;`;
1253
+ cta.style.cssText = el2.variant === "outline" ? `margin-top:${fLayout === "row" ? "0" : "auto"};background:transparent;color:${brand};border:1px solid ${brand};border-radius:999px;padding:6px 10px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;` : `margin-top:${fLayout === "row" ? "0" : "auto"};background:${brand};color:#fff;border:none;border-radius:999px;padding:7px 10px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;`;
1121
1254
  }
1122
1255
  cta.addEventListener("click", (e) => {
1256
+ var _a;
1123
1257
  e.stopPropagation();
1124
- trackEvent(campaign.id, "clicked", { stepId: el.id || "add" });
1125
- if (inline) {
1126
- if (typeof window !== "undefined") {
1127
- window.dispatchEvent(
1128
- new CustomEvent("aegis:add-to-cart", {
1129
- detail: {
1130
- campaign_id: campaign.id,
1131
- product_id: meta.product_id,
1132
- sku: meta.sku,
1133
- title: card.title,
1134
- price: meta.price ?? card.body,
1135
- image_url: card.image_url,
1136
- cta_url: card.cta_url
1137
- }
1138
- })
1139
- );
1140
- }
1141
- return;
1142
- }
1143
- if (card.cta_url) {
1144
- const safe = sanitizeUrl(card.cta_url);
1145
- if (safe) ctx.navigate ? ctx.navigate(safe) : window.location.href = safe;
1146
- }
1258
+ trackEvent(campaign.id, "clicked", { stepId: el2.id || "add" });
1259
+ (_a = ctx.dispatchAction) == null ? void 0 : _a.call(ctx, buildCtaAction(el2, card, meta, inline, sanitizeUrl));
1147
1260
  });
1148
1261
  (overlayOnImage ? imageWrap : container).appendChild(cta);
1149
1262
  break;
@@ -1153,27 +1266,20 @@ function renderCardFromTemplate(card, opts) {
1153
1266
  };
1154
1267
  paintInto(tile, elements);
1155
1268
  const open = (e) => {
1269
+ var _a;
1156
1270
  e.stopPropagation();
1157
1271
  trackEvent(campaign.id, "clicked");
1158
- if (inline) {
1159
- if (typeof window !== "undefined") {
1160
- window.dispatchEvent(
1161
- new CustomEvent("aegis:open-product", {
1162
- detail: {
1163
- campaign_id: campaign.id,
1164
- product_id: meta.product_id,
1165
- sku: meta.sku,
1166
- cta_url: card.cta_url
1167
- }
1168
- })
1169
- );
1170
- }
1171
- return;
1172
- }
1173
- if (card.cta_url) {
1174
- const safe = sanitizeUrl(card.cta_url);
1175
- if (safe) ctx.navigate ? ctx.navigate(safe) : window.location.href = safe;
1176
- }
1272
+ const bm = meta;
1273
+ const safe = (u) => u ? sanitizeUrl(u) || void 0 : void 0;
1274
+ const act = inline ? {
1275
+ type: "open_product",
1276
+ productId: bm.product_id,
1277
+ sku: bm.sku,
1278
+ url: safe(bm.platform_url) || safe(card.cta_url),
1279
+ platformProductId: bm.platform_product_id,
1280
+ platformUrl: safe(bm.platform_url)
1281
+ } : { type: "navigate", url: safe(card.cta_url) || "" };
1282
+ (_a = ctx.dispatchAction) == null ? void 0 : _a.call(ctx, act);
1177
1283
  };
1178
1284
  if (inline || card.cta_url) tile.addEventListener("click", open);
1179
1285
  if (frame.celebrate_on_show === true && ctx.playReaction) {
@@ -1206,6 +1312,7 @@ function renderCarouselCards(ctx) {
1206
1312
  }
1207
1313
  const autoplay = Number(ic.autoplay_ms) || 0;
1208
1314
  const loop = ic.loop !== false;
1315
+ const layout = ic.rec_layout || "row";
1209
1316
  addAnimationStyles();
1210
1317
  ensureInAppFont();
1211
1318
  const bg = "#ffffff";
@@ -1253,20 +1360,26 @@ function renderCarouselCards(ctx) {
1253
1360
  header.appendChild(closeBtn);
1254
1361
  card.appendChild(header);
1255
1362
  const track = document.createElement("div");
1256
- track.style.cssText = `
1257
- display: flex; gap: 10px; overflow-x: auto; scroll-snap-type: x proximity;
1258
- scroll-padding-left: 0; scrollbar-width: none; -ms-overflow-style: none;
1259
- /* room so overflow-x: auto doesn't clip the card drop-shadow */
1260
- padding: 8px 6px 16px; margin: -8px -6px -6px; -webkit-overflow-scrolling: touch;
1261
- `;
1262
- track.style.msOverflowStyle = "none";
1363
+ if (layout === "grid") {
1364
+ track.style.cssText = "display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; padding: 4px 2px;";
1365
+ } else if (layout === "list") {
1366
+ track.style.cssText = "display: flex; flex-direction: column; gap: 10px; padding: 4px 2px;";
1367
+ } else {
1368
+ track.style.cssText = `
1369
+ display: flex; gap: 10px; overflow-x: auto; scroll-snap-type: x proximity;
1370
+ scroll-padding-left: 0; scrollbar-width: none; -ms-overflow-style: none;
1371
+ /* room so overflow-x: auto doesn't clip the card drop-shadow */
1372
+ padding: 8px 6px 16px; margin: -8px -6px -6px; -webkit-overflow-scrolling: touch;
1373
+ `;
1374
+ track.style.msOverflowStyle = "none";
1375
+ }
1263
1376
  const template = resolveGlobalTemplate(ic);
1264
1377
  cards.forEach((c, i) => {
1265
1378
  const tile = renderCardFromTemplate(c, {
1266
1379
  template,
1267
1380
  ctx,
1268
1381
  action: "navigate",
1269
- layout: "row",
1382
+ layout,
1270
1383
  layoutAccent: accent
1271
1384
  });
1272
1385
  tile.setAttribute("data-card-index", String(i));
@@ -1276,6 +1389,12 @@ function renderCarouselCards(ctx) {
1276
1389
  track.appendChild(tile);
1277
1390
  });
1278
1391
  card.appendChild(track);
1392
+ if (layout !== "row") {
1393
+ cards.forEach((_, i) => trackEvent(campaign.id, "impression", { stepId: `card_${i}` }));
1394
+ overlay.appendChild(card);
1395
+ document.body.appendChild(overlay);
1396
+ return;
1397
+ }
1279
1398
  const dots = document.createElement("div");
1280
1399
  dots.style.cssText = "display: flex; justify-content: center; gap: 6px; margin-top: 10px;";
1281
1400
  const dotEls = [];
@@ -1305,9 +1424,9 @@ function renderCarouselCards(ctx) {
1305
1424
  const tiles = track.querySelectorAll("[data-card-index]");
1306
1425
  const goto = (idx) => {
1307
1426
  activeIdx = (idx % cards.length + cards.length) % cards.length;
1308
- const el = tiles[activeIdx];
1309
- if (el) {
1310
- const delta = el.getBoundingClientRect().left - track.getBoundingClientRect().left;
1427
+ const el2 = tiles[activeIdx];
1428
+ if (el2) {
1429
+ const delta = el2.getBoundingClientRect().left - track.getBoundingClientRect().left;
1311
1430
  track.scrollTo({ left: track.scrollLeft + delta, behavior: "smooth" });
1312
1431
  setActive(activeIdx);
1313
1432
  }
@@ -1924,6 +2043,10 @@ function renderProductRecommendation(ctx) {
1924
2043
  display: flex; gap: 10px; overflow-x: auto;
1925
2044
  scroll-snap-type: x mandatory; padding: 14px 2px 18px;
1926
2045
  `;
2046
+ } else if (layout === "list") {
2047
+ grid.style.cssText = `
2048
+ display: flex; flex-direction: column; gap: 10px; padding: 14px 2px 6px;
2049
+ `;
1927
2050
  } else {
1928
2051
  grid.style.cssText = `
1929
2052
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;
@@ -1980,9 +2103,11 @@ function renderProductRecommendationInline(ctx, target) {
1980
2103
  section.appendChild(body);
1981
2104
  }
1982
2105
  const grid = document.createElement("div");
1983
- const isRow = layout !== "grid";
2106
+ const isRow = layout === "row" || layout !== "grid" && layout !== "list";
1984
2107
  if (layout === "grid") {
1985
2108
  grid.style.cssText = "display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;";
2109
+ } else if (layout === "list") {
2110
+ grid.style.cssText = "display: flex; flex-direction: column; gap: 10px;";
1986
2111
  } else {
1987
2112
  grid.style.cssText = `
1988
2113
  display: flex; gap: 10px; overflow-x: auto;
@@ -2525,10 +2650,10 @@ function brandAccent(ctx) {
2525
2650
  function injectGameStyles() {
2526
2651
  if (typeof document === "undefined" || document.getElementById("aegis-game-styles")) return;
2527
2652
  if (typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
2528
- const el = document.createElement("style");
2529
- el.id = "aegis-game-styles";
2530
- el.textContent = "@keyframes aegisBoxRattle{0%,100%{transform:scale(1) rotate(0)}20%{transform:scale(1.05) rotate(-4deg)}40%{transform:scale(1.05) rotate(4deg)}60%{transform:scale(1.06) rotate(-3deg)}80%{transform:scale(1.06) rotate(3deg)}}@keyframes aegisStampIn{0%{transform:scale(2.4) rotate(-20deg);opacity:0}55%{transform:scale(0.82) rotate(7deg);opacity:1}100%{transform:scale(1) rotate(-6deg);opacity:1}}@keyframes aegisBadgePop{0%{transform:scale(0) rotate(-12deg);opacity:0}70%{transform:scale(1.18) rotate(4deg);opacity:1}100%{transform:scale(1) rotate(0);opacity:1}}";
2531
- document.head.appendChild(el);
2653
+ const el2 = document.createElement("style");
2654
+ el2.id = "aegis-game-styles";
2655
+ el2.textContent = "@keyframes aegisBoxRattle{0%,100%{transform:scale(1) rotate(0)}20%{transform:scale(1.05) rotate(-4deg)}40%{transform:scale(1.05) rotate(4deg)}60%{transform:scale(1.06) rotate(-3deg)}80%{transform:scale(1.06) rotate(3deg)}}@keyframes aegisStampIn{0%{transform:scale(2.4) rotate(-20deg);opacity:0}55%{transform:scale(0.82) rotate(7deg);opacity:1}100%{transform:scale(1) rotate(-6deg);opacity:1}}@keyframes aegisBadgePop{0%{transform:scale(0) rotate(-12deg);opacity:0}70%{transform:scale(1.18) rotate(4deg);opacity:1}100%{transform:scale(1) rotate(0);opacity:1}}";
2656
+ document.head.appendChild(el2);
2532
2657
  }
2533
2658
  function openGameModal(ctx, className, maxWidth = 360) {
2534
2659
  const { campaign, trackEvent, addAnimationStyles } = ctx;
@@ -2623,10 +2748,10 @@ function renderStreak(ctx) {
2623
2748
  s.content.appendChild(gEl("div", "font-size:22px;font-weight:800;margin:4px 0 2px", `${count}-day streak!`));
2624
2749
  const chips = gEl("div", "display:flex;justify-content:center;gap:8px;margin:12px 0");
2625
2750
  for (let i = 0; i < 7; i++) {
2626
- const chip = gEl("div", "display:inline-flex;flex-direction:column;align-items:center;gap:3px;font-size:11px;opacity:0.8");
2627
- chip.appendChild(gEl("span", "", days[i]));
2628
- chip.appendChild(gEl("span", "font-size:16px", i < count % 7 || count >= 7 && i < 7 ? "✅" : i === count % 7 ? "🔥" : "⚪"));
2629
- chips.appendChild(chip);
2751
+ const chip2 = gEl("div", "display:inline-flex;flex-direction:column;align-items:center;gap:3px;font-size:11px;opacity:0.8");
2752
+ chip2.appendChild(gEl("span", "", days[i]));
2753
+ chip2.appendChild(gEl("span", "font-size:16px", i < count % 7 || count >= 7 && i < 7 ? "✅" : i === count % 7 ? "🔥" : "⚪"));
2754
+ chips.appendChild(chip2);
2630
2755
  }
2631
2756
  s.content.appendChild(chips);
2632
2757
  const note = gEl("div", "font-size:13px;opacity:0.8");
@@ -3991,15 +4116,15 @@ class AegisChat {
3991
4116
  row.className = "aegis-chat-quickreplies";
3992
4117
  for (const label of this.quickReplies) {
3993
4118
  if (typeof label !== "string" || !label.trim()) continue;
3994
- const chip = document.createElement("button");
3995
- chip.type = "button";
3996
- chip.className = "aegis-chat-chip";
3997
- chip.textContent = label;
3998
- chip.addEventListener("click", () => {
4119
+ const chip2 = document.createElement("button");
4120
+ chip2.type = "button";
4121
+ chip2.className = "aegis-chat-chip";
4122
+ chip2.textContent = label;
4123
+ chip2.addEventListener("click", () => {
3999
4124
  row.remove();
4000
4125
  void this.send(label);
4001
4126
  });
4002
- row.appendChild(chip);
4127
+ row.appendChild(chip2);
4003
4128
  }
4004
4129
  if (row.childElementCount > 0) this.thread.appendChild(row);
4005
4130
  }
@@ -4036,8 +4161,8 @@ class AegisChat {
4036
4161
  }
4037
4162
  if (msg.interactive && Array.isArray(msg.interactive)) {
4038
4163
  for (const inter of msg.interactive) {
4039
- const el = this.renderInteractive(inter);
4040
- if (el) row.appendChild(el);
4164
+ const el2 = this.renderInteractive(inter);
4165
+ if (el2) row.appendChild(el2);
4041
4166
  }
4042
4167
  }
4043
4168
  this.thread.appendChild(row);
@@ -4901,6 +5026,408 @@ function disposeActiveWebChat() {
4901
5026
  activeInstance == null ? void 0 : activeInstance.destroy();
4902
5027
  activeInstance = null;
4903
5028
  }
5029
+ const SVG_NS = "http://www.w3.org/2000/svg";
5030
+ function el(tag, css, text) {
5031
+ const e = document.createElement(tag);
5032
+ if (css) e.style.cssText = css;
5033
+ if (text != null) e.textContent = text;
5034
+ return e;
5035
+ }
5036
+ function stars(rating, size = 14) {
5037
+ const wrap = el("span", "display:inline-flex;gap:1px;vertical-align:middle;");
5038
+ wrap.setAttribute("aria-label", `${rating} out of 5`);
5039
+ for (let i = 1; i <= 5; i++) {
5040
+ const svg = document.createElementNS(SVG_NS, "svg");
5041
+ svg.setAttribute("viewBox", "0 0 24 24");
5042
+ svg.setAttribute("width", String(size));
5043
+ svg.setAttribute("height", String(size));
5044
+ const path = document.createElementNS(SVG_NS, "path");
5045
+ path.setAttribute(
5046
+ "d",
5047
+ "M12 2l2.9 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l7.1-1.01L12 2z"
5048
+ );
5049
+ path.setAttribute("fill", i <= rating ? "#f59e0b" : "#e2e8f0");
5050
+ svg.appendChild(path);
5051
+ wrap.appendChild(svg);
5052
+ }
5053
+ return wrap;
5054
+ }
5055
+ function chip(text, kind) {
5056
+ const palette = kind === "verified" ? "background:#ecfdf5;color:#047857;" : "background:#fffbeb;color:#b45309;";
5057
+ return el(
5058
+ "span",
5059
+ `display:inline-flex;align-items:center;font-size:11px;font-weight:600;padding:2px 8px;border-radius:999px;${palette}`,
5060
+ text
5061
+ );
5062
+ }
5063
+ function sparkles() {
5064
+ const svg = document.createElementNS(SVG_NS, "svg");
5065
+ svg.setAttribute("viewBox", "0 0 24 24");
5066
+ svg.setAttribute("width", "15");
5067
+ svg.setAttribute("height", "15");
5068
+ const p = document.createElementNS(SVG_NS, "path");
5069
+ p.setAttribute("d", "M12 3l1.6 4.4L18 9l-4.4 1.6L12 15l-1.6-4.4L6 9l4.4-1.6L12 3z");
5070
+ p.setAttribute("fill", "#7c3aed");
5071
+ svg.appendChild(p);
5072
+ return svg;
5073
+ }
5074
+ function miniStar() {
5075
+ const svg = document.createElementNS(SVG_NS, "svg");
5076
+ svg.setAttribute("viewBox", "0 0 24 24");
5077
+ svg.setAttribute("width", "11");
5078
+ svg.setAttribute("height", "11");
5079
+ const p = document.createElementNS(SVG_NS, "path");
5080
+ p.setAttribute(
5081
+ "d",
5082
+ "M12 2l2.9 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l7.1-1.01L12 2z"
5083
+ );
5084
+ p.setAttribute("fill", "#f59e0b");
5085
+ svg.appendChild(p);
5086
+ return svg;
5087
+ }
5088
+ function distributionRows(dist, total) {
5089
+ const box = el("div", "display:flex;flex-direction:column;gap:3px;margin:10px 0 4px;");
5090
+ for (let r = 5; r >= 1; r--) {
5091
+ const n = dist[String(r)] ?? 0;
5092
+ const pct = total > 0 ? Math.round(n / total * 100) : 0;
5093
+ const row = el("div", "display:flex;align-items:center;gap:8px;font-size:12px;color:#64748b;");
5094
+ const label = el("span", "width:26px;display:inline-flex;align-items:center;gap:2px;color:#334155;", String(r));
5095
+ label.appendChild(miniStar());
5096
+ row.appendChild(label);
5097
+ const track = el("div", "flex:1;height:6px;background:#f1f5f9;border-radius:999px;overflow:hidden;");
5098
+ track.appendChild(el("div", `width:${pct}%;height:100%;background:#f59e0b;`));
5099
+ row.appendChild(track);
5100
+ row.appendChild(el("span", "width:28px;text-align:right;", String(n)));
5101
+ box.appendChild(row);
5102
+ }
5103
+ return box;
5104
+ }
5105
+ function injectJsonLd(target, data, productId) {
5106
+ if (!data.aggregate.count || data.aggregate.avg_rating == null) return;
5107
+ const ld = {
5108
+ "@context": "https://schema.org",
5109
+ "@type": "Product",
5110
+ ...productId ? { sku: productId } : {},
5111
+ aggregateRating: {
5112
+ "@type": "AggregateRating",
5113
+ ratingValue: data.aggregate.avg_rating,
5114
+ reviewCount: data.aggregate.count
5115
+ },
5116
+ review: data.items.slice(0, 10).map((r) => {
5117
+ var _a;
5118
+ return {
5119
+ "@type": "Review",
5120
+ reviewRating: { "@type": "Rating", ratingValue: r.rating ?? 0 },
5121
+ author: { "@type": "Person", name: ((_a = r.reviewer_snapshot) == null ? void 0 : _a.name) ?? "Customer" },
5122
+ ...r.review_text ? { reviewBody: r.review_text } : {}
5123
+ };
5124
+ })
5125
+ };
5126
+ const script = document.createElement("script");
5127
+ script.type = "application/ld+json";
5128
+ script.textContent = JSON.stringify(ld);
5129
+ target.appendChild(script);
5130
+ }
5131
+ function thumbSvg(up) {
5132
+ const svg = document.createElementNS(SVG_NS, "svg");
5133
+ svg.setAttribute("viewBox", "0 0 24 24");
5134
+ svg.setAttribute("width", "14");
5135
+ svg.setAttribute("height", "14");
5136
+ if (!up) svg.style.cssText = "transform:rotate(180deg);";
5137
+ const p = document.createElementNS(SVG_NS, "path");
5138
+ p.setAttribute(
5139
+ "d",
5140
+ "M2 21h4V9H2v12zM23 10a2 2 0 0 0-2-2h-6.31l.95-4.57.03-.32a1.5 1.5 0 0 0-.44-1.06L14.17 1 7.59 7.59A2 2 0 0 0 7 9v10a2 2 0 0 0 2 2h9a2 2 0 0 0 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1z"
5141
+ );
5142
+ p.setAttribute("fill", "currentColor");
5143
+ svg.appendChild(p);
5144
+ return svg;
5145
+ }
5146
+ function helpfulButton(reviewId, up, count, creds) {
5147
+ const btn = el(
5148
+ "button",
5149
+ "display:inline-flex;align-items:center;gap:4px;font-size:12px;color:#64748b;background:none;border:none;cursor:pointer;padding:2px 4px;"
5150
+ );
5151
+ btn.appendChild(thumbSvg(up));
5152
+ const countEl = el("span", "", String(count));
5153
+ btn.appendChild(countEl);
5154
+ const voteKey = `aegis_rv_voted_${reviewId}`;
5155
+ const already = (() => {
5156
+ try {
5157
+ return typeof localStorage !== "undefined" && localStorage.getItem(voteKey) != null;
5158
+ } catch {
5159
+ return false;
5160
+ }
5161
+ })();
5162
+ if (already) btn.style.opacity = "0.5";
5163
+ btn.addEventListener("click", () => {
5164
+ try {
5165
+ if (localStorage.getItem(voteKey) != null) return;
5166
+ localStorage.setItem(voteKey, up ? "up" : "down");
5167
+ } catch {
5168
+ }
5169
+ countEl.textContent = String(count + 1);
5170
+ btn.style.opacity = "0.5";
5171
+ const url = new URL(`/v1/reviews/${reviewId}/helpful`, creds.apiHost);
5172
+ void fetch(url.toString(), {
5173
+ method: "POST",
5174
+ headers: { "X-Aegis-Write-Key": creds.writeKey, "Content-Type": "application/json" },
5175
+ body: JSON.stringify({ direction: up ? "up" : "down" })
5176
+ }).catch(() => {
5177
+ });
5178
+ });
5179
+ return btn;
5180
+ }
5181
+ function reviewCard(ctx, r, creds) {
5182
+ var _a, _b;
5183
+ const li = el("li", "padding:14px 0;border-top:1px solid #e2e8f0;");
5184
+ const meta = el("div", "display:flex;flex-wrap:wrap;align-items:center;gap:8px;");
5185
+ meta.appendChild(stars(r.rating ?? 0));
5186
+ meta.appendChild(el("span", "font-size:14px;font-weight:600;", ((_a = r.reviewer_snapshot) == null ? void 0 : _a.name) ?? "Customer"));
5187
+ if (r.verified_purchase) meta.appendChild(chip("Verified purchase", "verified"));
5188
+ if (r.is_incentivized) meta.appendChild(chip("Reward included", "reward"));
5189
+ if (r.created_at) {
5190
+ const d = new Date(r.created_at);
5191
+ if (!Number.isNaN(d.getTime())) {
5192
+ meta.appendChild(el("span", "margin-left:auto;font-size:12px;color:#94a3b8;", d.toLocaleDateString()));
5193
+ }
5194
+ }
5195
+ li.appendChild(meta);
5196
+ if (r.review_text) {
5197
+ li.appendChild(el("p", "margin:6px 0 0;font-size:14px;line-height:1.5;color:#334155;", r.review_text));
5198
+ }
5199
+ if (r.media && r.media.length > 0) {
5200
+ const gallery = el("div", "display:flex;gap:6px;margin-top:8px;");
5201
+ for (const m2 of r.media.slice(0, 6)) {
5202
+ const safe = ctx.sanitizeUrl(m2.url);
5203
+ if (!safe) continue;
5204
+ const img = document.createElement("img");
5205
+ img.src = safe;
5206
+ img.loading = "lazy";
5207
+ img.alt = "";
5208
+ img.style.cssText = "width:56px;height:56px;object-fit:cover;border-radius:8px;";
5209
+ gallery.appendChild(img);
5210
+ }
5211
+ if (gallery.childElementCount) li.appendChild(gallery);
5212
+ }
5213
+ if ((_b = r.operator_reply) == null ? void 0 : _b.text) {
5214
+ const reply = el(
5215
+ "div",
5216
+ "margin-top:8px;padding:8px 10px;background:#f8fafc;border-radius:8px;font-size:13px;color:#475569;"
5217
+ );
5218
+ reply.appendChild(el("span", "font-weight:600;margin-right:6px;", "Reply"));
5219
+ reply.appendChild(el("span", "", r.operator_reply.text));
5220
+ li.appendChild(reply);
5221
+ }
5222
+ const votes = el("div", "display:flex;align-items:center;gap:10px;margin-top:8px;");
5223
+ votes.appendChild(el("span", "font-size:12px;color:#94a3b8;", "Helpful?"));
5224
+ votes.appendChild(helpfulButton(r.id, true, r.helpful_count ?? 0, creds));
5225
+ votes.appendChild(helpfulButton(r.id, false, r.not_helpful_count ?? 0, creds));
5226
+ li.appendChild(votes);
5227
+ return li;
5228
+ }
5229
+ const SORTS = [
5230
+ { key: "recent", label: "Most recent" },
5231
+ { key: "helpful", label: "Most helpful" },
5232
+ { key: "rating", label: "Highest rated" }
5233
+ ];
5234
+ function renderReviewsInline(ctx, target, creds) {
5235
+ var _a;
5236
+ if (typeof document === "undefined" || !(creds == null ? void 0 : creds.apiHost) || !(creds == null ? void 0 : creds.writeKey)) return false;
5237
+ const productId = target.getAttribute("data-product-id") || ctx.campaign.product_id || null;
5238
+ const state = { sort: "recent", withPhotos: false };
5239
+ target.innerHTML = "";
5240
+ target.appendChild(el("div", "font-size:13px;color:#94a3b8;padding:8px 0;", "Loading reviews…"));
5241
+ function buildUrl() {
5242
+ const url = new URL("/v1/reviews/public", creds.apiHost);
5243
+ if (productId) url.searchParams.set("product_id", productId);
5244
+ url.searchParams.set("sort", state.sort);
5245
+ if (state.withPhotos) url.searchParams.set("with_photos", "true");
5246
+ url.searchParams.set("limit", "20");
5247
+ return url.toString();
5248
+ }
5249
+ function fetchReviews() {
5250
+ return fetch(buildUrl(), { method: "GET", headers: { "X-Aegis-Write-Key": creds.writeKey } }).then((r) => r.ok ? r.json() : null).catch(() => null);
5251
+ }
5252
+ let listContainer = null;
5253
+ function renderList(items) {
5254
+ if (!listContainer) return;
5255
+ listContainer.innerHTML = "";
5256
+ if (items.length === 0) {
5257
+ listContainer.appendChild(el("p", "padding:14px 0;font-size:13px;color:#94a3b8;", "No reviews match this filter."));
5258
+ return;
5259
+ }
5260
+ const ul = el("ul", "list-style:none;margin:0;padding:0;");
5261
+ for (const r of items) ul.appendChild(reviewCard(ctx, r, creds));
5262
+ listContainer.appendChild(ul);
5263
+ }
5264
+ function controls(onChange) {
5265
+ const bar = el("div", "display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:10px 0;");
5266
+ const sel = document.createElement("select");
5267
+ sel.style.cssText = "font-size:13px;padding:4px 8px;border:1px solid #e2e8f0;border-radius:999px;background:#fff;color:#334155;";
5268
+ for (const s of SORTS) {
5269
+ const opt = document.createElement("option");
5270
+ opt.value = s.key;
5271
+ opt.textContent = s.label;
5272
+ sel.appendChild(opt);
5273
+ }
5274
+ sel.value = state.sort;
5275
+ sel.addEventListener("change", () => {
5276
+ state.sort = sel.value;
5277
+ onChange();
5278
+ });
5279
+ bar.appendChild(sel);
5280
+ const photos = el(
5281
+ "button",
5282
+ "font-size:13px;padding:4px 12px;border:1px solid #e2e8f0;border-radius:999px;background:#fff;color:#334155;cursor:pointer;",
5283
+ "With photos"
5284
+ );
5285
+ photos.addEventListener("click", () => {
5286
+ state.withPhotos = !state.withPhotos;
5287
+ photos.style.cssText = "font-size:13px;padding:4px 12px;border-radius:999px;cursor:pointer;" + (state.withPhotos ? "background:#4169e1;color:#fff;border:1px solid #4169e1;" : "background:#fff;color:#334155;border:1px solid #e2e8f0;");
5288
+ onChange();
5289
+ });
5290
+ bar.appendChild(photos);
5291
+ return bar;
5292
+ }
5293
+ function renderWall(data) {
5294
+ target.innerHTML = "";
5295
+ const root = el(
5296
+ "section",
5297
+ "font-family:Inter,system-ui,-apple-system,sans-serif;color:#0f172a;max-width:760px;"
5298
+ );
5299
+ root.setAttribute("data-aegis-reviews", "true");
5300
+ const summary = data.aggregate.ai_summary;
5301
+ if (summary && summary.text) {
5302
+ const card = el(
5303
+ "div",
5304
+ "border:1px solid #ede9fe;background:#faf5ff;border-radius:12px;padding:12px 14px;margin-bottom:14px;"
5305
+ );
5306
+ const h = el("div", "display:flex;align-items:center;gap:6px;margin-bottom:6px;");
5307
+ h.appendChild(sparkles());
5308
+ h.appendChild(el("span", "font-size:12px;font-weight:700;color:#7c3aed;text-transform:uppercase;letter-spacing:.04em;", "AI summary"));
5309
+ card.appendChild(h);
5310
+ card.appendChild(el("p", "margin:0;font-size:14px;line-height:1.5;color:#334155;", summary.text));
5311
+ card.appendChild(
5312
+ el("p", "margin:6px 0 0;font-size:11px;color:#a78bda;text-align:right;", `summarized from ${summary.review_count} reviews`)
5313
+ );
5314
+ root.appendChild(card);
5315
+ }
5316
+ const head = el("div", "display:flex;align-items:center;gap:8px;");
5317
+ const avg = data.aggregate.avg_rating ?? 0;
5318
+ head.appendChild(stars(Math.round(avg), 18));
5319
+ head.appendChild(el("span", "font-size:20px;font-weight:700;", avg ? avg.toFixed(1) : "—"));
5320
+ head.appendChild(
5321
+ el("span", "font-size:13px;color:#64748b;", `${data.aggregate.count} review${data.aggregate.count === 1 ? "" : "s"}`)
5322
+ );
5323
+ root.appendChild(head);
5324
+ if (data.aggregate.count === 0) {
5325
+ root.appendChild(el("p", "margin-top:8px;font-size:14px;color:#94a3b8;", "No reviews yet."));
5326
+ target.appendChild(root);
5327
+ return;
5328
+ }
5329
+ root.appendChild(distributionRows(data.aggregate.distribution, data.aggregate.count));
5330
+ root.appendChild(
5331
+ controls(() => {
5332
+ void fetchReviews().then((d) => renderList((d == null ? void 0 : d.items) ?? []));
5333
+ })
5334
+ );
5335
+ listContainer = el("div");
5336
+ root.appendChild(listContainer);
5337
+ renderList(data.items);
5338
+ target.appendChild(root);
5339
+ injectJsonLd(target, data, productId);
5340
+ ctx.trackEvent(ctx.campaign.id, "impression");
5341
+ }
5342
+ const displayStyle = (_a = ctx.campaign.interactive_config) == null ? void 0 : _a["display_style"];
5343
+ const compact = displayStyle === "compact";
5344
+ function renderCompact(data) {
5345
+ var _a2;
5346
+ target.innerHTML = "";
5347
+ if (data.aggregate.count === 0) return;
5348
+ const root = el(
5349
+ "section",
5350
+ "font-family:Inter,system-ui,-apple-system,sans-serif;color:#0f172a;max-width:760px;border:1px solid #e2e8f0;border-radius:12px;padding:12px 14px;"
5351
+ );
5352
+ const line = el("div", "display:flex;align-items:center;gap:8px;");
5353
+ const avg = data.aggregate.avg_rating ?? 0;
5354
+ line.appendChild(stars(Math.round(avg), 16));
5355
+ line.appendChild(el("span", "font-size:15px;font-weight:700;", avg ? avg.toFixed(1) : "—"));
5356
+ line.appendChild(el("span", "font-size:13px;color:#64748b;", `(${data.aggregate.count})`));
5357
+ line.appendChild(chip("Verified reviews", "verified"));
5358
+ root.appendChild(line);
5359
+ const top = data.items.find((i) => i.review_text);
5360
+ if (top && top.review_text) {
5361
+ const q = el("p", "margin:8px 0 0;font-size:14px;color:#334155;", `“${top.review_text}”`);
5362
+ root.appendChild(q);
5363
+ root.appendChild(
5364
+ el("span", "font-size:12px;color:#94a3b8;", `— ${((_a2 = top.reviewer_snapshot) == null ? void 0 : _a2.name) ?? "Customer"}`)
5365
+ );
5366
+ }
5367
+ const seeAll = el(
5368
+ "button",
5369
+ "margin-top:10px;font-size:13px;font-weight:600;color:#4169e1;background:none;border:none;cursor:pointer;padding:0;",
5370
+ "See all reviews"
5371
+ );
5372
+ seeAll.addEventListener("click", () => renderWall(data));
5373
+ root.appendChild(seeAll);
5374
+ target.appendChild(root);
5375
+ ctx.trackEvent(ctx.campaign.id, "impression");
5376
+ }
5377
+ void fetchReviews().then((data) => {
5378
+ if (!data || !Array.isArray(data.items)) {
5379
+ target.innerHTML = "";
5380
+ return;
5381
+ }
5382
+ if (compact) renderCompact(data);
5383
+ else renderWall(data);
5384
+ });
5385
+ return true;
5386
+ }
5387
+ function renderReviewsOverlay(ctx, creds) {
5388
+ if (typeof document === "undefined" || !(creds == null ? void 0 : creds.apiHost) || !(creds == null ? void 0 : creds.writeKey)) return;
5389
+ const overlay = document.createElement("div");
5390
+ overlay.setAttribute("data-campaign-id", ctx.campaign.id);
5391
+ overlay.style.cssText = "position:fixed;inset:0;background:rgba(15,23,42,0.55);display:flex;align-items:flex-end;justify-content:center;z-index:99999;";
5392
+ const sheet = document.createElement("div");
5393
+ sheet.style.cssText = "background:#fff;color:#0f172a;max-width:620px;width:100%;max-height:82vh;overflow-y:auto;border-radius:20px 20px 0 0;padding:16px 16px 24px;font-family:Inter,system-ui,-apple-system,sans-serif;box-shadow:0 -12px 30px rgba(0,0,0,0.12);";
5394
+ const handle = el("div", "width:36px;height:4px;border-radius:999px;background:#e2e8f0;margin:0 auto 12px;");
5395
+ sheet.appendChild(handle);
5396
+ const bar = el("div", "display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;");
5397
+ bar.appendChild(el("div", "font-weight:700;font-size:16px;", ctx.campaign.title || "Customer reviews"));
5398
+ const close = el("button", "background:none;border:none;cursor:pointer;padding:4px 8px;color:#64748b;");
5399
+ const xsvg = document.createElementNS(SVG_NS, "svg");
5400
+ xsvg.setAttribute("viewBox", "0 0 24 24");
5401
+ xsvg.setAttribute("width", "18");
5402
+ xsvg.setAttribute("height", "18");
5403
+ const xp = document.createElementNS(SVG_NS, "path");
5404
+ xp.setAttribute("d", "M18 6L6 18M6 6l12 12");
5405
+ xp.setAttribute("stroke", "currentColor");
5406
+ xp.setAttribute("stroke-width", "2");
5407
+ xp.setAttribute("stroke-linecap", "round");
5408
+ xsvg.appendChild(xp);
5409
+ close.appendChild(xsvg);
5410
+ close.setAttribute("aria-label", "Close");
5411
+ close.addEventListener("click", () => {
5412
+ ctx.trackEvent(ctx.campaign.id, "dismissed");
5413
+ overlay.remove();
5414
+ });
5415
+ bar.appendChild(close);
5416
+ sheet.appendChild(bar);
5417
+ const body = el("div");
5418
+ const pid = ctx.campaign.product_id;
5419
+ if (pid) body.setAttribute("data-product-id", pid);
5420
+ sheet.appendChild(body);
5421
+ overlay.appendChild(sheet);
5422
+ overlay.addEventListener("click", (e) => {
5423
+ if (e.target === overlay) {
5424
+ ctx.trackEvent(ctx.campaign.id, "dismissed");
5425
+ overlay.remove();
5426
+ }
5427
+ });
5428
+ document.body.appendChild(overlay);
5429
+ renderReviewsInline(ctx, body, creds);
5430
+ }
4904
5431
  function readAnonIdFromStorage$1() {
4905
5432
  if (typeof document === "undefined") return void 0;
4906
5433
  try {
@@ -4922,6 +5449,7 @@ function svgNode(svg) {
4922
5449
  }
4923
5450
  const _AegisInAppManager = class _AegisInAppManager {
4924
5451
  constructor(config) {
5452
+ this.productUrl = (sku) => `/p/${encodeURIComponent(sku)}`;
4925
5453
  this.campaigns = [];
4926
5454
  this.displayedCampaigns = /* @__PURE__ */ new Set();
4927
5455
  this._displaySizeMul = 1;
@@ -4951,6 +5479,8 @@ const _AegisInAppManager = class _AegisInAppManager {
4951
5479
  this.debugMode = config.debugMode || false;
4952
5480
  this.enableSSE = config.enableSSE === true;
4953
5481
  this.onInteractiveCampaign = config.onInteractiveCampaign;
5482
+ this.onActionHandler = config.onAction;
5483
+ if (config.productUrl) this.productUrl = config.productUrl;
4954
5484
  this.getWorkspaceId = config.getWorkspaceId;
4955
5485
  this.getCartState = config.getCartState;
4956
5486
  }
@@ -5525,8 +6055,8 @@ const _AegisInAppManager = class _AegisInAppManager {
5525
6055
  m2.addedNodes.forEach((n) => {
5526
6056
  var _a, _b;
5527
6057
  if (sawSlot || n.nodeType !== 1) return;
5528
- const el = n;
5529
- if (((_a = el.matches) == null ? void 0 : _a.call(el, "[data-aegis-slot]")) || ((_b = el.querySelector) == null ? void 0 : _b.call(el, "[data-aegis-slot]"))) {
6058
+ const el2 = n;
6059
+ if (((_a = el2.matches) == null ? void 0 : _a.call(el2, "[data-aegis-slot]")) || ((_b = el2.querySelector) == null ? void 0 : _b.call(el2, "[data-aegis-slot]"))) {
5530
6060
  sawSlot = true;
5531
6061
  }
5532
6062
  });
@@ -5733,13 +6263,13 @@ const _AegisInAppManager = class _AegisInAppManager {
5733
6263
  const anchor = ic == null ? void 0 : ic.anchor;
5734
6264
  const selector = anchor == null ? void 0 : anchor.element_selector;
5735
6265
  if (!selector) continue;
5736
- let el = null;
6266
+ let el2 = null;
5737
6267
  try {
5738
- el = document.querySelector(selector);
6268
+ el2 = document.querySelector(selector);
5739
6269
  } catch {
5740
- el = null;
6270
+ el2 = null;
5741
6271
  }
5742
- if (!el) {
6272
+ if (!el2) {
5743
6273
  void this.trackEvent(c.id, "anchor_unresolved", {
5744
6274
  meta: { anchor_resolved: false, anchor_selector: selector, widget_type: c.sub_type }
5745
6275
  });
@@ -5750,9 +6280,9 @@ const _AegisInAppManager = class _AegisInAppManager {
5750
6280
  if (position === "before" || position === "after") {
5751
6281
  target = document.createElement("div");
5752
6282
  target.setAttribute("data-aegis-anchor-wrapper", c.id);
5753
- (_a = el.parentNode) == null ? void 0 : _a.insertBefore(target, position === "before" ? el : el.nextSibling);
6283
+ (_a = el2.parentNode) == null ? void 0 : _a.insertBefore(target, position === "before" ? el2 : el2.nextSibling);
5754
6284
  } else {
5755
- target = el;
6285
+ target = el2;
5756
6286
  if (position === "replace") {
5757
6287
  target.querySelectorAll(":scope > [data-aegis-slot-default]").forEach((d) => d.remove());
5758
6288
  }
@@ -5909,6 +6439,12 @@ const _AegisInAppManager = class _AegisInAppManager {
5909
6439
  case "product_recommendation":
5910
6440
  rendered = renderProductRecommendationInline(this.buildRenderContext(campaign), target);
5911
6441
  break;
6442
+ case "reviews":
6443
+ rendered = renderReviewsInline(this.buildRenderContext(campaign), target, {
6444
+ apiHost: this.apiHost,
6445
+ writeKey: this.writeKey
6446
+ });
6447
+ break;
5912
6448
  case "quiz":
5913
6449
  rendered = this.renderQuizSlot(campaign, ic, bg, text, target);
5914
6450
  if (!rendered) {
@@ -6116,8 +6652,8 @@ const _AegisInAppManager = class _AegisInAppManager {
6116
6652
  void this.trackEvent(campaign.id, "clicked");
6117
6653
  if (actionUrl) {
6118
6654
  window.open(actionUrl, "_blank", "noopener");
6119
- } else if (typeof window !== "undefined") {
6120
- window.dispatchEvent(new CustomEvent("aegis:open-login", { detail: { campaign_id: campaign.id } }));
6655
+ } else {
6656
+ this.dispatchAction({ type: "open_login" }, campaign.id);
6121
6657
  }
6122
6658
  });
6123
6659
  row.appendChild(btn);
@@ -6141,10 +6677,10 @@ const _AegisInAppManager = class _AegisInAppManager {
6141
6677
  cbody.appendChild(b);
6142
6678
  }
6143
6679
  if (couponCode) {
6144
- const chip = document.createElement("span");
6145
- chip.style.cssText = `align-self: flex-start; border: 1px dashed ${text}66; border-radius: 6px; padding: 2px 8px; font-family: monospace; font-size: 11px; letter-spacing: 0.04em;`;
6146
- chip.textContent = couponCode;
6147
- cbody.appendChild(chip);
6680
+ const chip2 = document.createElement("span");
6681
+ chip2.style.cssText = `align-self: flex-start; border: 1px dashed ${text}66; border-radius: 6px; padding: 2px 8px; font-family: monospace; font-size: 11px; letter-spacing: 0.04em;`;
6682
+ chip2.textContent = couponCode;
6683
+ cbody.appendChild(chip2);
6148
6684
  }
6149
6685
  if (buttonText) {
6150
6686
  const btn = document.createElement("button");
@@ -6727,19 +7263,19 @@ const _AegisInAppManager = class _AegisInAppManager {
6727
7263
  ov.appendChild(eyebrow);
6728
7264
  ov.appendChild(big);
6729
7265
  if (r.code) {
6730
- const chip = document.createElement("button");
6731
- chip.type = "button";
6732
- chip.textContent = `${r.code} ⧉`;
6733
- chip.style.cssText = `margin-top: 4px; padding: 9px 16px; border-radius: 999px; border: 1.5px dashed ${text}66; background: transparent; color: ${text}; font-weight: 800; font-size: 14px; letter-spacing: 1px; cursor: pointer;`;
6734
- chip.addEventListener("click", () => {
7266
+ const chip2 = document.createElement("button");
7267
+ chip2.type = "button";
7268
+ chip2.textContent = `${r.code} ⧉`;
7269
+ chip2.style.cssText = `margin-top: 4px; padding: 9px 16px; border-radius: 999px; border: 1.5px dashed ${text}66; background: transparent; color: ${text}; font-weight: 800; font-size: 14px; letter-spacing: 1px; cursor: pointer;`;
7270
+ chip2.addEventListener("click", () => {
6735
7271
  var _a;
6736
7272
  try {
6737
7273
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(r.code));
6738
- chip.textContent = "Copied";
7274
+ chip2.textContent = "Copied";
6739
7275
  } catch {
6740
7276
  }
6741
7277
  });
6742
- ov.appendChild(chip);
7278
+ ov.appendChild(chip2);
6743
7279
  } else if (r.kind === "freebie") {
6744
7280
  const hint = document.createElement("div");
6745
7281
  hint.textContent = "🎟️ Show this screen to claim";
@@ -6773,20 +7309,20 @@ const _AegisInAppManager = class _AegisInAppManager {
6773
7309
  result.textContent = "";
6774
7310
  const tag = document.createElement("span");
6775
7311
  tag.textContent = `${r.label} — `;
6776
- const chip = document.createElement("button");
6777
- chip.type = "button";
6778
- chip.textContent = `${r.code} ⧉`;
6779
- chip.style.cssText = `padding: 2px 8px; border-radius: 999px; border: 1.5px dashed ${text}66; background: transparent; color: ${text}; font-weight: 800; font-size: 12px; letter-spacing: 0.5px; cursor: pointer;`;
6780
- chip.addEventListener("click", () => {
7312
+ const chip2 = document.createElement("button");
7313
+ chip2.type = "button";
7314
+ chip2.textContent = `${r.code} ⧉`;
7315
+ chip2.style.cssText = `padding: 2px 8px; border-radius: 999px; border: 1.5px dashed ${text}66; background: transparent; color: ${text}; font-weight: 800; font-size: 12px; letter-spacing: 0.5px; cursor: pointer;`;
7316
+ chip2.addEventListener("click", () => {
6781
7317
  var _a;
6782
7318
  try {
6783
7319
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(r.code));
6784
- chip.textContent = "Copied";
7320
+ chip2.textContent = "Copied";
6785
7321
  } catch {
6786
7322
  }
6787
7323
  });
6788
7324
  result.appendChild(tag);
6789
- result.appendChild(chip);
7325
+ result.appendChild(chip2);
6790
7326
  } else result.textContent = r.label || "Thanks for playing!";
6791
7327
  retireCta();
6792
7328
  };
@@ -6961,17 +7497,27 @@ const _AegisInAppManager = class _AegisInAppManager {
6961
7497
  h.textContent = campaign.title;
6962
7498
  card.appendChild(h);
6963
7499
  }
7500
+ const layout = ic.rec_layout || "row";
6964
7501
  const strip = document.createElement("div");
6965
- strip.style.cssText = `display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory; padding: ${st.padY ?? 12}px ${st.padX ?? 12}px;`;
7502
+ const pad = `${st.padY ?? 12}px ${st.padX ?? 12}px`;
7503
+ if (layout === "grid") {
7504
+ strip.style.cssText = `display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; padding: ${pad};`;
7505
+ } else if (layout === "list") {
7506
+ strip.style.cssText = `display: flex; flex-direction: column; gap: 8px; padding: ${pad};`;
7507
+ } else {
7508
+ strip.style.cssText = `display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; scrollbar-width: none; padding: ${pad};`;
7509
+ }
6966
7510
  const template = resolveGlobalTemplate(ic);
6967
7511
  const ctx = this.buildRenderContext(campaign);
6968
7512
  const accent = this.sanitizeColor(campaign.background_color || text);
6969
7513
  cards.forEach((c, i) => {
7514
+ const meta = c.metadata || {};
7515
+ const linked = !!(meta.product_id || meta.sku);
6970
7516
  const tile = renderCardFromTemplate(c, {
6971
7517
  template,
6972
7518
  ctx,
6973
- action: "navigate",
6974
- layout: "row",
7519
+ action: linked ? "cart" : "navigate",
7520
+ layout,
6975
7521
  layoutAccent: accent
6976
7522
  });
6977
7523
  tile.setAttribute("data-card-index", String(i));
@@ -6979,6 +7525,43 @@ const _AegisInAppManager = class _AegisInAppManager {
6979
7525
  });
6980
7526
  card.appendChild(strip);
6981
7527
  target.appendChild(card);
7528
+ const autoplayMs = Number(ic.autoplay_ms) || 0;
7529
+ if (layout === "row" && autoplayMs > 0 && cards.length > 1) {
7530
+ const loop = ic.loop !== false;
7531
+ const interval = Math.max(autoplayMs, 600);
7532
+ let idx = 0;
7533
+ let timer = null;
7534
+ const stop = () => {
7535
+ if (timer) {
7536
+ clearInterval(timer);
7537
+ timer = null;
7538
+ }
7539
+ };
7540
+ timer = setInterval(() => {
7541
+ if (!document.body.contains(strip)) {
7542
+ stop();
7543
+ return;
7544
+ }
7545
+ if (strip.scrollWidth <= strip.clientWidth + 4) return;
7546
+ const tiles = Array.from(strip.children);
7547
+ idx += 1;
7548
+ if (idx >= tiles.length) {
7549
+ if (!loop) {
7550
+ stop();
7551
+ return;
7552
+ }
7553
+ idx = 0;
7554
+ }
7555
+ const el2 = tiles[idx];
7556
+ if (el2) {
7557
+ const delta = el2.getBoundingClientRect().left - strip.getBoundingClientRect().left;
7558
+ strip.scrollTo({ left: strip.scrollLeft + delta, behavior: "smooth" });
7559
+ }
7560
+ }, interval);
7561
+ ["pointerdown", "wheel", "touchstart"].forEach(
7562
+ (evt) => strip.addEventListener(evt, stop, { passive: true })
7563
+ );
7564
+ }
6982
7565
  return true;
6983
7566
  }
6984
7567
  /** Built-in embedded COUNTDOWN — live H:M:S digits that pulse red in the
@@ -7001,10 +7584,10 @@ const _AegisInAppManager = class _AegisInAppManager {
7001
7584
  const digits = document.createElement("div");
7002
7585
  digits.style.cssText = "display: flex; gap: 6px; justify-content: center;";
7003
7586
  const mkDigit = () => {
7004
- const el = document.createElement("span");
7005
- el.style.cssText = `padding: 6px 9px; border-radius: 8px; font-size: 18px; font-weight: 700; font-family: ui-monospace, monospace; background: ${text}1a; transition: background 0.3s ease, color 0.3s ease;`;
7006
- el.textContent = "00";
7007
- return el;
7587
+ const el2 = document.createElement("span");
7588
+ el2.style.cssText = `padding: 6px 9px; border-radius: 8px; font-size: 18px; font-weight: 700; font-family: ui-monospace, monospace; background: ${text}1a; transition: background 0.3s ease, color 0.3s ease;`;
7589
+ el2.textContent = "00";
7590
+ return el2;
7008
7591
  };
7009
7592
  const hEl = mkDigit();
7010
7593
  const mEl = mkDigit();
@@ -7034,9 +7617,9 @@ const _AegisInAppManager = class _AegisInAppManager {
7034
7617
  sEl.textContent = String(s).padStart(2, "0");
7035
7618
  if (diff > 0 && diff <= 6e4 && !urgent) {
7036
7619
  urgent = true;
7037
- [hEl, mEl, sEl].forEach((el) => {
7038
- el.style.boxShadow = "0 0 0 2px #ef4444";
7039
- el.style.animation = "aegisUrgencyPulse 1s ease-in-out infinite";
7620
+ [hEl, mEl, sEl].forEach((el2) => {
7621
+ el2.style.boxShadow = "0 0 0 2px #ef4444";
7622
+ el2.style.animation = "aegisUrgencyPulse 1s ease-in-out infinite";
7040
7623
  });
7041
7624
  }
7042
7625
  if (diff > 0) requestAnimationFrame(tick);
@@ -7391,19 +7974,19 @@ const _AegisInAppManager = class _AegisInAppManager {
7391
7974
  ov.appendChild(eb);
7392
7975
  ov.appendChild(big);
7393
7976
  if (code) {
7394
- const chip = document.createElement("button");
7395
- chip.type = "button";
7396
- chip.textContent = `${code} ⧉`;
7397
- chip.style.cssText = `margin-top: 4px; padding: 8px 14px; border-radius: 999px; border: 1.5px dashed ${text}66; background: transparent; color: ${text}; font-weight: 800; font-size: 13px; letter-spacing: 1px; cursor: pointer;`;
7398
- chip.addEventListener("click", () => {
7977
+ const chip2 = document.createElement("button");
7978
+ chip2.type = "button";
7979
+ chip2.textContent = `${code} ⧉`;
7980
+ chip2.style.cssText = `margin-top: 4px; padding: 8px 14px; border-radius: 999px; border: 1.5px dashed ${text}66; background: transparent; color: ${text}; font-weight: 800; font-size: 13px; letter-spacing: 1px; cursor: pointer;`;
7981
+ chip2.addEventListener("click", () => {
7399
7982
  var _a;
7400
7983
  try {
7401
7984
  void ((_a = navigator.clipboard) == null ? void 0 : _a.writeText(code));
7402
- chip.textContent = "Copied";
7985
+ chip2.textContent = "Copied";
7403
7986
  } catch {
7404
7987
  }
7405
7988
  });
7406
- ov.appendChild(chip);
7989
+ ov.appendChild(chip2);
7407
7990
  }
7408
7991
  card.appendChild(ov);
7409
7992
  try {
@@ -7413,10 +7996,10 @@ const _AegisInAppManager = class _AegisInAppManager {
7413
7996
  } else {
7414
7997
  prizeText.textContent = icoVal && !isUrl(icoVal) ? `${icoVal} ${label}` : label;
7415
7998
  if (code) {
7416
- const chip = document.createElement("div");
7417
- chip.style.cssText = `margin-top: 6px; font-family: ui-monospace, monospace; font-size: 12px; font-weight: 700; padding: 4px 8px; border-radius: 6px; background: ${text}14; color: ${text};`;
7418
- chip.textContent = code;
7419
- body.appendChild(chip);
7999
+ const chip2 = document.createElement("div");
8000
+ chip2.style.cssText = `margin-top: 6px; font-family: ui-monospace, monospace; font-size: 12px; font-weight: 700; padding: 4px 8px; border-radius: 6px; background: ${text}14; color: ${text};`;
8001
+ chip2.textContent = code;
8002
+ body.appendChild(chip2);
7420
8003
  }
7421
8004
  this._playReaction(body, "celebrate", "gift.json", ic);
7422
8005
  }
@@ -7578,9 +8161,9 @@ const _AegisInAppManager = class _AegisInAppManager {
7578
8161
  */
7579
8162
  _flattenSlotCard(container) {
7580
8163
  const cards = container.matches("[data-campaign-id]") ? [container] : Array.from(container.querySelectorAll("[data-campaign-id]"));
7581
- for (const el of cards) {
7582
- el.style.borderRadius = "0";
7583
- el.style.boxShadow = "none";
8164
+ for (const el2 of cards) {
8165
+ el2.style.borderRadius = "0";
8166
+ el2.style.boxShadow = "none";
7584
8167
  }
7585
8168
  }
7586
8169
  /**
@@ -7666,6 +8249,15 @@ const _AegisInAppManager = class _AegisInAppManager {
7666
8249
  this.emit("campaign-shown", campaign);
7667
8250
  return;
7668
8251
  }
8252
+ if (campaign.sub_type === "reviews") {
8253
+ renderReviewsOverlay(this.buildRenderContext(campaign), {
8254
+ apiHost: this.apiHost,
8255
+ writeKey: this.writeKey
8256
+ });
8257
+ this.trackEvent(campaign.id, "impression");
8258
+ this.emit("campaign-shown", campaign);
8259
+ return;
8260
+ }
7669
8261
  {
7670
8262
  const ic2 = campaign.interactive_config || {};
7671
8263
  const multiCard2 = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
@@ -7791,8 +8383,9 @@ const _AegisInAppManager = class _AegisInAppManager {
7791
8383
  },
7792
8384
  sanitizeUrl: (url) => this.sanitizeUrl(url),
7793
8385
  navigate: (url, buttonId) => this.navigateToCampaignAction(campaign, url, buttonId),
8386
+ dispatchAction: (action) => this.dispatchAction(action, campaign.id),
7794
8387
  submitResponse: (responseType, payload) => this.submitWidgetResponse(campaign, responseType, payload),
7795
- playReaction: (el, intent, lottieFile, scope) => this._playReaction(el, intent, lottieFile, campaign.interactive_config, scope),
8388
+ playReaction: (el2, intent, lottieFile, scope) => this._playReaction(el2, intent, lottieFile, campaign.interactive_config, scope),
7796
8389
  haptic: (pattern) => {
7797
8390
  try {
7798
8391
  if (typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
@@ -8049,9 +8642,9 @@ const _AegisInAppManager = class _AegisInAppManager {
8049
8642
  if (cells.s) cells.s.textContent = pad(s);
8050
8643
  if (!urgent && diff > 0 && diff <= 6e4) {
8051
8644
  urgent = true;
8052
- Object.values(cells).forEach((el) => {
8053
- if (el) {
8054
- el.style.boxShadow = "0 0 0 2px #ef4444";
8645
+ Object.values(cells).forEach((el2) => {
8646
+ if (el2) {
8647
+ el2.style.boxShadow = "0 0 0 2px #ef4444";
8055
8648
  }
8056
8649
  });
8057
8650
  digits.style.animation = "aegisCountdownPulse 1.1s ease-in-out infinite";
@@ -8566,13 +9159,42 @@ const _AegisInAppManager = class _AegisInAppManager {
8566
9159
  window.location.href = safeUrl;
8567
9160
  }
8568
9161
  }
9162
+ /** Register/replace the canonical CTA-action handler after construction (the
9163
+ * config `onAction` is the construct-time equivalent). Idempotent. */
9164
+ setOnAction(handler) {
9165
+ this.onActionHandler = handler;
9166
+ }
9167
+ /**
9168
+ * Canonical CTA-action dispatcher (CANONICAL_ACTION_MODEL_TRACKER.md). ONE path
9169
+ * for every host-facing CTA action:
9170
+ * 1. give the host's `onAction` handler first refusal (return `true` = handled);
9171
+ * 2. ALSO fire the legacy `aegis:*` DOM event (cancelable) so existing
9172
+ * listeners (ACS) keep working unchanged during the migration;
9173
+ * 3. if STILL unhandled → canonical fallback (navigate to the product/cart
9174
+ * page) so a CTA is never a dead no-op (the rail-"Add" 3rd-party gap).
9175
+ * Both add-to-cart paths (card "Add" + the `aegis://cart-add` verb) funnel here,
9176
+ * so they're one behaviour delivered to the host's single `onAction` handler.
9177
+ * (Phase E.2 hard cutover — the legacy `aegis:*` DOM events were removed.)
9178
+ */
9179
+ dispatchAction(action, _campaignId) {
9180
+ var _a;
9181
+ let handled = false;
9182
+ try {
9183
+ handled = ((_a = this.onActionHandler) == null ? void 0 : _a.call(this, action)) === true;
9184
+ } catch {
9185
+ }
9186
+ if (!handled) {
9187
+ const url = fallbackUrlForAction(action, this.productUrl);
9188
+ if (url && typeof window !== "undefined") window.location.href = url;
9189
+ }
9190
+ }
8569
9191
  /**
8570
9192
  * Phase-2 CTA action verbs (`aegis://<verb>?<params>`). Performs an in-app
8571
9193
  * behaviour instead of navigating. Verbs:
8572
9194
  * dismiss — close the message (tracked as dismissed)
8573
9195
  * request-push — prompt for web-push permission, then close
8574
- * cart-add?sku=X — dispatch `aegis:cart-add` for the storefront to handle;
8575
- * if unhandled, fall back to the product page
9196
+ * cart-add?sku=X — canonical `add_to_cart` action via dispatchAction
9197
+ * (cancelable + product-page fallback)
8576
9198
  */
8577
9199
  runCampaignActionVerb(campaign, url) {
8578
9200
  const rest = url.slice("aegis://".length);
@@ -8595,14 +9217,7 @@ const _AegisInAppManager = class _AegisInAppManager {
8595
9217
  case "cart-add": {
8596
9218
  const sku = params.get("sku") || "";
8597
9219
  void this.trackEvent(campaign.id, "clicked", { meta: { verb: "cart-add", sku } });
8598
- const evt = new CustomEvent("aegis:cart-add", {
8599
- detail: { sku, qty: 1, campaign_id: campaign.id },
8600
- cancelable: true
8601
- });
8602
- const proceed = window.dispatchEvent(evt);
8603
- if (proceed && !evt.defaultPrevented && sku) {
8604
- window.location.href = `/p/${encodeURIComponent(sku)}`;
8605
- }
9220
+ this.dispatchAction(parseAction(url, { sku }), campaign.id);
8606
9221
  break;
8607
9222
  }
8608
9223
  default:
@@ -8612,9 +9227,9 @@ const _AegisInAppManager = class _AegisInAppManager {
8612
9227
  /** Remove a rendered campaign's element(s) from the DOM by campaign id. */
8613
9228
  removeCampaignEl(campaignId) {
8614
9229
  if (typeof document === "undefined") return;
8615
- document.querySelectorAll(`[data-campaign-id="${campaignId}"]`).forEach((el) => {
9230
+ document.querySelectorAll(`[data-campaign-id="${campaignId}"]`).forEach((el2) => {
8616
9231
  var _a;
8617
- return (_a = el.parentNode) == null ? void 0 : _a.removeChild(el);
9232
+ return (_a = el2.parentNode) == null ? void 0 : _a.removeChild(el2);
8618
9233
  });
8619
9234
  }
8620
9235
  /**
@@ -9966,7 +10581,8 @@ const _AegisInAppManager = class _AegisInAppManager {
9966
10581
  this.addAnimationStyles();
9967
10582
  const reduce = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
9968
10583
  const viewport = scope === "viewport";
9969
- const anchor = viewport ? document.body : host || document.body;
10584
+ const cardAnchor = !viewport && (host == null ? void 0 : host.closest) ? host.closest("[data-aegis-reaction-anchor]") : null;
10585
+ const anchor = viewport ? document.body : cardAnchor || host || document.body;
9970
10586
  const prevPos = anchor.style.position;
9971
10587
  if (!viewport && (!prevPos || prevPos === "static")) anchor.style.position = "relative";
9972
10588
  const layer = document.createElement("div");
@@ -10005,7 +10621,7 @@ const _AegisInAppManager = class _AegisInAppManager {
10005
10621
  g.style.cssText = `position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: ${px}px; height: ${px}px; display: flex; align-items: center; justify-content: center; line-height: 0; filter: drop-shadow(0 8px 16px rgba(0,0,0,0.30)); ${motion}`;
10006
10622
  layer.appendChild(g);
10007
10623
  anchor.appendChild(layer);
10008
- if (festive && !reduce) this._fireConfetti(host);
10624
+ if (festive && !reduce) this._fireConfetti(cardAnchor || host);
10009
10625
  const ra = ic && typeof ic === "object" ? ic.reaction_animations : void 0;
10010
10626
  const custom = ra && typeof ra[intent] === "string" ? ra[intent] : void 0;
10011
10627
  const file = custom || lottieFile || spec.file;
@@ -10340,9 +10956,9 @@ const _AegisInAppManager = class _AegisInAppManager {
10340
10956
  if (typeof document !== "undefined") {
10341
10957
  document.querySelectorAll(
10342
10958
  ".aegis-in-app-banner, .aegis-in-app-modal-overlay, .aegis-in-app-fullscreen-overlay, .aegis-in-app-half-interstitial-overlay, .aegis-in-app-alert-overlay, .aegis-in-app-pip, .aegis-in-app-nps-overlay, .aegis-in-app-countdown-overlay, .aegis-in-app-rating-overlay, .aegis-in-app-poll-overlay, .aegis-in-app-quiz-overlay, .aegis-in-app-rating-card, .aegis-in-app-nps-card"
10343
- ).forEach((el) => {
10344
- if (el.parentNode) {
10345
- el.parentNode.removeChild(el);
10959
+ ).forEach((el2) => {
10960
+ if (el2.parentNode) {
10961
+ el2.parentNode.removeChild(el2);
10346
10962
  }
10347
10963
  });
10348
10964
  const styles = document.getElementById("aegis-in-app-styles");
@@ -10374,9 +10990,9 @@ let AegisInAppManager = _AegisInAppManager;
10374
10990
  function renderPreview(config, opts) {
10375
10991
  document.querySelectorAll(
10376
10992
  '[class^="aegis-in-app-"]'
10377
- ).forEach((el) => {
10378
- if (el.parentNode) {
10379
- el.parentNode.removeChild(el);
10993
+ ).forEach((el2) => {
10994
+ if (el2.parentNode) {
10995
+ el2.parentNode.removeChild(el2);
10380
10996
  }
10381
10997
  });
10382
10998
  try {
@@ -10412,7 +11028,7 @@ function renderPreview(config, opts) {
10412
11028
  if (anchors.length > 0) {
10413
11029
  try {
10414
11030
  anchors.forEach((slot) => {
10415
- slot.querySelectorAll(":scope > :not([data-aegis-slot-default])").forEach((el) => el.remove());
11031
+ slot.querySelectorAll(":scope > :not([data-aegis-slot-default])").forEach((el2) => el2.remove());
10416
11032
  });
10417
11033
  const mm = manager;
10418
11034
  mm.campaigns = (inline ? [config, ...coGroup] : coGroup).map((c) => ({ ...c, surface: void 0, target_screens: void 0 })).sort((a, b) => (b.priority || 0) - (a.priority || 0));
@@ -10433,17 +11049,17 @@ function renderPreview(config, opts) {
10433
11049
  m2.displayCampaign(config);
10434
11050
  }
10435
11051
  const MAX_NODES = 400;
10436
- function selectorFor(el) {
10437
- if (el.id) return `#${CSS.escape(el.id)}`;
11052
+ function selectorFor(el2) {
11053
+ if (el2.id) return `#${CSS.escape(el2.id)}`;
10438
11054
  for (const attr of ["data-testid", "data-test", "data-aegis-anchor", "data-qa", "name"]) {
10439
- const v = el.getAttribute(attr);
11055
+ const v = el2.getAttribute(attr);
10440
11056
  if (v) {
10441
- const sel = `${el.tagName.toLowerCase()}[${attr}="${CSS.escape(v)}"]`;
11057
+ const sel = `${el2.tagName.toLowerCase()}[${attr}="${CSS.escape(v)}"]`;
10442
11058
  if (document.querySelectorAll(sel).length === 1) return sel;
10443
11059
  }
10444
11060
  }
10445
11061
  const parts = [];
10446
- let node = el;
11062
+ let node = el2;
10447
11063
  while (node && node !== document.body && parts.length < 6) {
10448
11064
  if (node.id) {
10449
11065
  parts.unshift(`#${CSS.escape(node.id)}`);
@@ -10467,15 +11083,15 @@ function captureWebScreen(screenName) {
10467
11083
  const candidates = document.querySelectorAll(
10468
11084
  "a,button,input,select,textarea,[role],[data-aegis-anchor],[data-testid],h1,h2,h3,img,[data-aegis-slot]"
10469
11085
  );
10470
- for (const el of Array.from(candidates)) {
11086
+ for (const el2 of Array.from(candidates)) {
10471
11087
  if (nodes.length >= MAX_NODES) break;
10472
- const rect = el.getBoundingClientRect();
11088
+ const rect = el2.getBoundingClientRect();
10473
11089
  if (rect.width <= 0 || rect.height <= 0) continue;
10474
- const slotKey = el.getAttribute("data-aegis-slot");
10475
- const role = slotKey ? `slot:${slotKey}` : el.getAttribute("role") || el.tagName.toLowerCase();
10476
- const text = (el.textContent || el.getAttribute("aria-label") || "").trim().slice(0, 80);
11090
+ const slotKey = el2.getAttribute("data-aegis-slot");
11091
+ const role = slotKey ? `slot:${slotKey}` : el2.getAttribute("role") || el2.tagName.toLowerCase();
11092
+ const text = (el2.textContent || el2.getAttribute("aria-label") || "").trim().slice(0, 80);
10477
11093
  nodes.push({
10478
- selector: selectorFor(el),
11094
+ selector: selectorFor(el2),
10479
11095
  role,
10480
11096
  ...text ? { text } : {},
10481
11097
  bounds: {
@@ -13636,7 +14252,13 @@ class AegisMessageRuntime {
13636
14252
  getWorkspaceId: config.getWorkspaceId,
13637
14253
  // First-party cart reader — renderers (progress-bar) read live cart
13638
14254
  // state from here instead of `window.*` cart globals. See setCartState().
13639
- getCartState: () => this.deriveCartState()
14255
+ getCartState: () => this.deriveCartState(),
14256
+ // Canonical CTA-action handler + platform product-URL resolver
14257
+ // (CANONICAL_ACTION_MODEL_TRACKER.md). A host (ACS / Shopify-Woo plugin /
14258
+ // native) registers ONE `onAction` to handle add_to_cart/open_product/…;
14259
+ // legacy `aegis:*` DOM events still fire for back-compat.
14260
+ onAction: config.onAction,
14261
+ productUrl: config.productUrl
13640
14262
  });
13641
14263
  this.intentRuleEvaluator = new IntentRuleEvaluator();
13642
14264
  if (config.governance) {
@@ -13788,6 +14410,17 @@ class AegisMessageRuntime {
13788
14410
  setCartState(state) {
13789
14411
  this.applyCart(state);
13790
14412
  }
14413
+ /**
14414
+ * Register the canonical CTA-action handler — the CEP-standard single callback
14415
+ * that receives every in-app intent (add_to_cart / open_product / open_cart /
14416
+ * open_login / navigate / custom). Return `true` when handled; otherwise the SDK
14417
+ * applies the graceful fallback. The construct-time `onAction` config is the
14418
+ * equivalent; this lets a host register/replace it once it holds the runtime.
14419
+ * See CANONICAL_ACTION_MODEL_TRACKER.md.
14420
+ */
14421
+ onAction(handler) {
14422
+ this.inApp.setOnAction(handler);
14423
+ }
13791
14424
  /**
13792
14425
  * The ONE cart writer. Normalizes any cart-ish shape and funnels it into the
13793
14426
  * single `cart` context namespace (read by the Free-Delivery progress card,
@@ -13995,9 +14628,11 @@ export {
13995
14628
  debounce,
13996
14629
  aegis as default,
13997
14630
  deriveDeviceFingerprint,
14631
+ fallbackUrlForAction,
13998
14632
  getCurrentLauncher,
13999
14633
  m as murmurhash3_x86_32,
14000
14634
  openChat,
14635
+ parseAction,
14001
14636
  readFirstPartyCookie,
14002
14637
  renderPreview,
14003
14638
  throttle,