@active-reach/web-sdk 1.29.0 → 1.31.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
@@ -311,12 +311,12 @@ const ICONS = {
311
311
  function escapeAttr(value) {
312
312
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
313
313
  }
314
- const SVG_NS = "http://www.w3.org/2000/svg";
314
+ const SVG_NS$1 = "http://www.w3.org/2000/svg";
315
315
  function lucideSvgEl(name, opts = {}) {
316
316
  const node = ICONS[name];
317
317
  if (!node || typeof document === "undefined") return null;
318
318
  const size = opts.size ?? 24;
319
- const svg = document.createElementNS(SVG_NS, "svg");
319
+ const svg = document.createElementNS(SVG_NS$1, "svg");
320
320
  svg.setAttribute("width", String(size));
321
321
  svg.setAttribute("height", String(size));
322
322
  svg.setAttribute("viewBox", "0 0 24 24");
@@ -333,7 +333,7 @@ function lucideSvgEl(name, opts = {}) {
333
333
  svg.setAttribute("aria-hidden", "true");
334
334
  }
335
335
  for (const [tag, attrs] of node) {
336
- const child = document.createElementNS(SVG_NS, tag);
336
+ const child = document.createElementNS(SVG_NS$1, tag);
337
337
  for (const [k, v] of Object.entries(attrs)) child.setAttribute(k, String(v));
338
338
  svg.appendChild(child);
339
339
  }
@@ -525,6 +525,16 @@ function applyPerCardOverride(tpl, look) {
525
525
  if (look.hide_cta) drop("cta");
526
526
  if (look.hide_rating) drop("rating");
527
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
+ }
528
538
  if (look.badge) {
529
539
  const badgeEl = { type: "badge", text: look.badge, color: look.badge_color };
530
540
  elements = elements.some((e) => e.type === "badge") ? elements.map((e) => e.type === "badge" ? badgeEl : e) : [elements[0], badgeEl, ...elements.slice(1)].filter(Boolean);
@@ -538,7 +548,7 @@ function priceText(card) {
538
548
  if (card.body && /\d/.test(card.body)) return card.body;
539
549
  return void 0;
540
550
  }
541
- function paintPrice(card, el, accent, ctx) {
551
+ function paintPrice(card, el2, accent, ctx) {
542
552
  const price = priceText(card);
543
553
  if (price === void 0) return null;
544
554
  const meta = card.metadata || {};
@@ -547,10 +557,10 @@ function paintPrice(card, el, accent, ctx) {
547
557
  row.style.cssText = "display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;";
548
558
  const p = document.createElement("span");
549
559
  p.textContent = price;
550
- if (el.variant === "plain") {
560
+ if (el2.variant === "plain") {
551
561
  p.style.cssText = `color:${accent};font-weight:700;font-size:13px;`;
552
562
  } else {
553
- const pillBg = el.color ? ctx.sanitizeColor(el.color) : "#059669";
563
+ const pillBg = el2.color ? ctx.sanitizeColor(el2.color) : "#059669";
554
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);`;
555
565
  }
556
566
  row.appendChild(p);
@@ -575,13 +585,13 @@ function paintRating(card) {
575
585
  const count = Number(meta.review_count ?? 0);
576
586
  const rr = document.createElement("div");
577
587
  rr.style.cssText = "display:flex;align-items:center;gap:3px;font-size:11px;line-height:1;";
578
- const star = document.createElement("span");
579
- star.textContent = "★";
580
- star.style.cssText = "color:#059669;";
588
+ const star2 = document.createElement("span");
589
+ star2.textContent = "★";
590
+ star2.style.cssText = "color:#059669;";
581
591
  const val = document.createElement("span");
582
592
  val.textContent = avg.toFixed(1);
583
593
  val.style.cssText = "color:#059669;font-weight:700;";
584
- rr.appendChild(star);
594
+ rr.appendChild(star2);
585
595
  rr.appendChild(val);
586
596
  if (count > 0) {
587
597
  const c = document.createElement("span");
@@ -602,14 +612,14 @@ function contrastFg(bg) {
602
612
  const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
603
613
  return lum < 0.5 ? "#ffffff" : "#1f2937";
604
614
  }
605
- function buildCountdownElement(el, fg, muted) {
606
- 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 : "";
607
617
  const target = targetStr ? Date.parse(targetStr) : NaN;
608
618
  if (!targetStr || isNaN(target)) return null;
609
619
  const wrap = document.createElement("div");
610
620
  wrap.style.cssText = "display:flex;align-items:baseline;gap:8px;font-variant-numeric:tabular-nums;margin:2px 0;";
611
621
  const label = document.createElement("span");
612
- 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";
613
623
  label.style.cssText = `font-size:12px;color:${muted};`;
614
624
  const time = document.createElement("span");
615
625
  time.style.cssText = `font-size:15px;font-weight:700;color:${fg};letter-spacing:0.2px;`;
@@ -631,13 +641,13 @@ function buildCountdownElement(el, fg, muted) {
631
641
  timer = setInterval(tick, 1e3);
632
642
  return wrap;
633
643
  }
634
- function appendRatingThanks(wrap, el, ctx, fg, accent, isHigh) {
635
- appendThanks(wrap, el, fg, "Thanks for rating us!");
636
- 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) : "";
637
647
  if (url) {
638
648
  const cta = document.createElement("button");
639
649
  cta.type = "button";
640
- cta.textContent = el.rating_high_cta || "Leave a review";
650
+ cta.textContent = el2.rating_high_cta || "Leave a review";
641
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;`;
642
652
  cta.addEventListener("click", () => {
643
653
  ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "rating_input", cta: "high_review" } });
@@ -647,10 +657,10 @@ function appendRatingThanks(wrap, el, ctx, fg, accent, isHigh) {
647
657
  wrap.appendChild(cta);
648
658
  }
649
659
  }
650
- function renderRatingFollowupStep(wrap, el, ctx, stars, fg, accent) {
660
+ function renderRatingFollowupStep(wrap, el2, ctx, stars2, fg, accent) {
651
661
  while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
652
662
  const heading = document.createElement("div");
653
- 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?";
654
664
  heading.style.cssText = `font-size:13px;font-weight:700;color:${fg};text-align:center;margin-bottom:8px;`;
655
665
  wrap.appendChild(heading);
656
666
  const ta = document.createElement("textarea");
@@ -664,9 +674,9 @@ function renderRatingFollowupStep(wrap, el, ctx, stars, fg, accent) {
664
674
  if (done) return;
665
675
  done = true;
666
676
  const comment = withComment ? ta.value.trim() : "";
667
- 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 }));
668
678
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, "acknowledge");
669
- appendThanks(wrap, el, fg, "Thanks — we’ll do better.");
679
+ appendThanks(wrap, el2, fg, "Thanks — we’ll do better.");
670
680
  };
671
681
  const btnRow = document.createElement("div");
672
682
  btnRow.style.cssText = "display:flex;gap:8px;justify-content:center;";
@@ -685,23 +695,23 @@ function renderRatingFollowupStep(wrap, el, ctx, stars, fg, accent) {
685
695
  wrap.appendChild(btnRow);
686
696
  ta.focus();
687
697
  }
688
- function buildRatingInputElement(el, ctx, fg) {
698
+ function buildRatingInputElement(el2, ctx, fg) {
689
699
  const accent = ctx.sanitizeColor(ctx.campaign.background_color || "#4169e1");
690
700
  const wrap = document.createElement("div");
691
701
  wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
692
- if (el.content) {
702
+ if (el2.content) {
693
703
  const q = document.createElement("div");
694
- q.textContent = el.content;
704
+ q.textContent = el2.content;
695
705
  q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
696
706
  wrap.appendChild(q);
697
707
  }
698
- 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;
699
709
  const row = document.createElement("div");
700
710
  row.style.cssText = "display:flex;gap:6px;";
701
- const stars = [];
711
+ const stars2 = [];
702
712
  let submitted = false;
703
713
  const paint = (upto) => {
704
- stars.forEach((s, idx) => {
714
+ stars2.forEach((s, idx) => {
705
715
  const on = idx < upto;
706
716
  s.setAttribute("stroke", on ? "#f59e0b" : "#d1d5db");
707
717
  s.setAttribute("fill", on ? "#f59e0b" : "none");
@@ -715,7 +725,7 @@ function buildRatingInputElement(el, ctx, fg) {
715
725
  const svg = lucideSvgEl("star", { size: 26, color: "#d1d5db" });
716
726
  if (svg) {
717
727
  btn.appendChild(svg);
718
- stars.push(svg);
728
+ stars2.push(svg);
719
729
  }
720
730
  btn.addEventListener("mouseenter", () => {
721
731
  if (!submitted) paint(i);
@@ -729,13 +739,13 @@ function buildRatingInputElement(el, ctx, fg) {
729
739
  submitted = true;
730
740
  paint(i);
731
741
  ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "rating_input", stars: i } });
732
- const lowThreshold = typeof el.rating_followup_threshold === "number" ? el.rating_followup_threshold : Math.ceil(scale * 0.8);
733
- if (el.rating_followup_enabled === true && i <= lowThreshold) {
734
- 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);
735
745
  } else {
736
746
  void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "rating", { stars: i }));
737
747
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, i >= Math.ceil(scale * 0.6) ? "affirm" : "acknowledge");
738
- 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));
739
749
  }
740
750
  });
741
751
  row.appendChild(btn);
@@ -743,8 +753,8 @@ function buildRatingInputElement(el, ctx, fg) {
743
753
  wrap.appendChild(row);
744
754
  return wrap;
745
755
  }
746
- function appendThanks(wrap, el, fg, fallback) {
747
- 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;
748
758
  if (!msg) return;
749
759
  const note = document.createElement("div");
750
760
  note.textContent = msg;
@@ -752,14 +762,14 @@ function appendThanks(wrap, el, fg, fallback) {
752
762
  note.style.cssText = `font-size:12px;font-weight:600;color:${fg};opacity:0.85;margin-top:4px;`;
753
763
  wrap.appendChild(note);
754
764
  }
755
- function buildPollElement(el, ctx, fg, accent) {
756
- 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) : [];
757
767
  if (opts.length < 2) return null;
758
768
  const wrap = document.createElement("div");
759
769
  wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
760
- if (el.content) {
770
+ if (el2.content) {
761
771
  const q = document.createElement("div");
762
- q.textContent = el.content;
772
+ q.textContent = el2.content;
763
773
  q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
764
774
  wrap.appendChild(q);
765
775
  }
@@ -789,24 +799,24 @@ function buildPollElement(el, ctx, fg, accent) {
789
799
  ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "poll", option_index: idx, option_label: label } });
790
800
  void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "poll", { option_index: idx, option_label: label }));
791
801
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, "affirm");
792
- appendThanks(wrap, el, fg, "Thanks for your answer!");
802
+ appendThanks(wrap, el2, fg, "Thanks for your answer!");
793
803
  });
794
804
  wrap.appendChild(btn);
795
805
  });
796
806
  return wrap;
797
807
  }
798
- function appendNpsThanks(wrap, el, ctx, fg, accent, isPromoter) {
808
+ function appendNpsThanks(wrap, el2, ctx, fg, accent, isPromoter) {
799
809
  while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
800
810
  const note = document.createElement("div");
801
811
  note.setAttribute("role", "status");
802
- 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.");
803
813
  note.style.cssText = `font-size:13px;font-weight:700;color:${fg};text-align:center;margin:6px 0;`;
804
814
  wrap.appendChild(note);
805
- 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) : "";
806
816
  if (url) {
807
817
  const cta = document.createElement("button");
808
818
  cta.type = "button";
809
- cta.textContent = el.nps_promoter_cta || "Leave a review";
819
+ cta.textContent = el2.nps_promoter_cta || "Leave a review";
810
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;`;
811
821
  cta.addEventListener("click", () => {
812
822
  ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "nps", cta: "promoter_review" } });
@@ -816,17 +826,17 @@ function appendNpsThanks(wrap, el, ctx, fg, accent, isPromoter) {
816
826
  wrap.appendChild(cta);
817
827
  }
818
828
  }
819
- function renderNpsReasonStep(wrap, el, ctx, score, fg, accent) {
820
- 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;
821
831
  const band = npsBandFromScore(score);
822
- 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?";
823
833
  while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
824
834
  const heading = document.createElement("div");
825
835
  heading.textContent = prompt;
826
836
  heading.style.cssText = `font-size:13px;font-weight:700;color:${fg};text-align:center;margin-bottom:8px;`;
827
837
  wrap.appendChild(heading);
828
838
  const ta = document.createElement("textarea");
829
- 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 : [];
830
840
  if (chips.length) {
831
841
  const chipRow = document.createElement("div");
832
842
  chipRow.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-bottom:8px;";
@@ -855,7 +865,7 @@ function renderNpsReasonStep(wrap, el, ctx, score, fg, accent) {
855
865
  const comment = withComment ? ta.value.trim() : "";
856
866
  void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "nps", comment ? { score, band, comment } : { score, band }));
857
867
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, score >= promoterThreshold ? "affirm" : score <= 6 ? "empathize" : "acknowledge");
858
- appendNpsThanks(wrap, el, ctx, fg, accent, score >= promoterThreshold);
868
+ appendNpsThanks(wrap, el2, ctx, fg, accent, score >= promoterThreshold);
859
869
  };
860
870
  const btnRow = document.createElement("div");
861
871
  btnRow.style.cssText = "display:flex;gap:8px;justify-content:center;";
@@ -874,12 +884,12 @@ function renderNpsReasonStep(wrap, el, ctx, score, fg, accent) {
874
884
  wrap.appendChild(btnRow);
875
885
  ta.focus();
876
886
  }
877
- function buildNpsElement(el, ctx, fg, accent, muted) {
887
+ function buildNpsElement(el2, ctx, fg, accent, muted) {
878
888
  const wrap = document.createElement("div");
879
889
  wrap.style.cssText = "display:flex;flex-direction:column;gap:6px;margin:2px 0;";
880
- if (el.content) {
890
+ if (el2.content) {
881
891
  const q = document.createElement("div");
882
- q.textContent = el.content;
892
+ q.textContent = el2.content;
883
893
  q.style.cssText = `font-size:13px;font-weight:600;color:${fg};`;
884
894
  wrap.appendChild(q);
885
895
  }
@@ -911,12 +921,12 @@ function buildNpsElement(el, ctx, fg, accent, muted) {
911
921
  btn.style.opacity = "1";
912
922
  const npsPayload = { score: n, band: npsBandFromScore(n) };
913
923
  ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "nps", score: n, band: npsPayload.band } });
914
- if (el.nps_followup_enabled === true) {
915
- renderNpsReasonStep(wrap, el, ctx, n, fg, accent);
924
+ if (el2.nps_followup_enabled === true) {
925
+ renderNpsReasonStep(wrap, el2, ctx, n, fg, accent);
916
926
  } else {
917
927
  void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "nps", { ...npsPayload }));
918
928
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, n >= 9 ? "affirm" : n >= 7 ? "acknowledge" : "empathize");
919
- appendThanks(wrap, el, fg, "Thanks for your feedback!");
929
+ appendThanks(wrap, el2, fg, "Thanks for your feedback!");
920
930
  }
921
931
  });
922
932
  row.appendChild(btn);
@@ -933,22 +943,22 @@ function buildNpsElement(el, ctx, fg, accent, muted) {
933
943
  wrap.appendChild(scale);
934
944
  return wrap;
935
945
  }
936
- function buildProgressElement(el, ctx, accent, muted) {
946
+ function buildProgressElement(el2, ctx, accent, muted) {
937
947
  const { trackEvent, campaign, sanitizeColor } = ctx;
938
- const goal = el.goal_type || "cart_total";
948
+ const goal = el2.goal_type || "cart_total";
939
949
  const moneyGoal = goal === "cart_total";
940
- const fill = el.fill ? sanitizeColor(el.fill) : accent;
950
+ const fill = el2.fill ? sanitizeColor(el2.fill) : accent;
941
951
  const props = () => ctx.properties && ctx.properties() || {};
942
952
  const sym = () => {
943
953
  var _a;
944
- if (el.currency_symbol) return el.currency_symbol;
954
+ if (el2.currency_symbol) return el2.currency_symbol;
945
955
  const code = ctx.getCartState && ((_a = ctx.getCartState()) == null ? void 0 : _a.currency);
946
956
  const M = { INR: "₹", USD: "$", EUR: "€", GBP: "£" };
947
957
  return code && M[code] || "";
948
958
  };
949
959
  const threshold = () => {
950
- if (typeof el.threshold === "number" && el.threshold > 0) return el.threshold;
951
- 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") {
952
962
  const v = Number(props()["cart.free_delivery_above"]);
953
963
  if (v > 0) return v;
954
964
  }
@@ -989,9 +999,9 @@ function buildProgressElement(el, ctx, accent, muted) {
989
999
  goalType: goal,
990
1000
  threshold: th,
991
1001
  current: cur,
992
- rewardText: el.reward_text,
1002
+ rewardText: el2.reward_text,
993
1003
  currencySymbol: moneyGoal ? sym() : "",
994
- showPct: el.show_pct
1004
+ showPct: el2.show_pct
995
1005
  });
996
1006
  sub.textContent = labeled.text;
997
1007
  if (complete) {
@@ -1000,8 +1010,8 @@ function buildProgressElement(el, ctx, accent, muted) {
1000
1010
  if (!firedUnlock) {
1001
1011
  firedUnlock = true;
1002
1012
  if (seenIncomplete) {
1003
- trackEvent(campaign.id, "clicked", { stepId: el.id || "unlocked" });
1004
- 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");
1005
1015
  }
1006
1016
  }
1007
1017
  } else {
@@ -1019,6 +1029,40 @@ function buildProgressElement(el, ctx, accent, muted) {
1019
1029
  }
1020
1030
  return wrap;
1021
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
+ }
1022
1066
  function renderCardFromTemplate(card, opts) {
1023
1067
  const { ctx, action, layout, layoutAccent } = opts;
1024
1068
  const inline = action === "cart";
@@ -1038,7 +1082,7 @@ function renderCardFromTemplate(card, opts) {
1038
1082
  const fLayout = frame.layout;
1039
1083
  const dir = fLayout === "row" ? "row" : "column";
1040
1084
  const tileGap = frame.gap != null ? frame.gap : 6;
1041
- 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;";
1042
1086
  tile.style.cssText = `
1043
1087
  box-sizing:border-box; display:flex; flex-direction:${dir}; ${fLayout === "row" ? "align-items:center;" : ""} gap:${tileGap}px; position:relative;
1044
1088
  color:${fg};
@@ -1055,11 +1099,11 @@ function renderCardFromTemplate(card, opts) {
1055
1099
  const elements = tpl.elements && tpl.elements.length ? tpl.elements : DEFAULT_CARD_TEMPLATE.elements;
1056
1100
  let imageWrap = null;
1057
1101
  const paintInto = (container, els) => {
1058
- for (const el of els) {
1059
- switch (el.type) {
1102
+ for (const el2 of els) {
1103
+ switch (el2.type) {
1060
1104
  case "image": {
1061
1105
  const wrap = document.createElement("div");
1062
- 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;`;
1063
1107
  const safeVideo = card.video_url ? sanitizeUrl(card.video_url) : "";
1064
1108
  if (safeVideo) {
1065
1109
  const v = document.createElement("video");
@@ -1087,17 +1131,17 @@ function renderCardFromTemplate(card, opts) {
1087
1131
  break;
1088
1132
  }
1089
1133
  case "badge": {
1090
- const text = el.text;
1134
+ const text = el2.text;
1091
1135
  if (!text) break;
1092
1136
  const badge = document.createElement("span");
1093
1137
  badge.textContent = text;
1094
- const bg = el.color ? sanitizeColor(el.color) : brand;
1138
+ const bg = el2.color ? sanitizeColor(el2.color) : brand;
1095
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;`;
1096
1140
  (imageWrap || container).appendChild(badge);
1097
1141
  break;
1098
1142
  }
1099
1143
  case "price": {
1100
- const node = paintPrice(card, el, accent, ctx);
1144
+ const node = paintPrice(card, el2, accent, ctx);
1101
1145
  if (node) container.appendChild(node);
1102
1146
  break;
1103
1147
  }
@@ -1105,8 +1149,8 @@ function renderCardFromTemplate(card, opts) {
1105
1149
  if (!card.title) break;
1106
1150
  const t = document.createElement("div");
1107
1151
  t.textContent = card.title;
1108
- const lines = el.lines && el.lines > 0 ? el.lines : 2;
1109
- 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;`;
1110
1154
  container.appendChild(t);
1111
1155
  break;
1112
1156
  }
@@ -1114,8 +1158,8 @@ function renderCardFromTemplate(card, opts) {
1114
1158
  if (!card.body) break;
1115
1159
  const s = document.createElement("div");
1116
1160
  s.textContent = card.body;
1117
- const lines = el.lines && el.lines > 0 ? el.lines : 2;
1118
- 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;`;
1119
1163
  container.appendChild(s);
1120
1164
  break;
1121
1165
  }
@@ -1135,17 +1179,17 @@ function renderCardFromTemplate(card, opts) {
1135
1179
  }
1136
1180
  // ── rich-card-editor elements (P0) ──────────────────────────────────
1137
1181
  case "icon": {
1138
- const px = ICON_PX[el.size || "md"] || 30;
1182
+ const px = ICON_PX[el2.size || "md"] || 30;
1139
1183
  const node = document.createElement("div");
1140
- const iconColor = el.color ? sanitizeColor(el.color) : fg;
1141
- if (el.bubble) {
1142
- 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;
1143
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);`;
1144
1188
  } else {
1145
1189
  node.style.cssText = "flex:0 0 auto;display:flex;align-items:center;";
1146
1190
  }
1147
- const svg = lucideSvgEl(el.icon_name || "circle-check", {
1148
- 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),
1149
1193
  color: iconColor,
1150
1194
  strokeWidth: 2
1151
1195
  });
@@ -1155,99 +1199,64 @@ function renderCardFromTemplate(card, opts) {
1155
1199
  }
1156
1200
  case "text": {
1157
1201
  const t = document.createElement("div");
1158
- const fs = TEXT_PX[el.size || "md"] || 13;
1159
- 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;`;
1160
- 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);
1161
1205
  container.appendChild(t);
1162
1206
  break;
1163
1207
  }
1164
1208
  case "spacer": {
1165
1209
  const sp = document.createElement("div");
1166
- sp.style.height = `${el.height ?? 8}px`;
1210
+ sp.style.height = `${el2.height ?? 8}px`;
1167
1211
  container.appendChild(sp);
1168
1212
  break;
1169
1213
  }
1170
1214
  case "progress": {
1171
- container.appendChild(buildProgressElement(el, ctx, brand, muted));
1215
+ container.appendChild(buildProgressElement(el2, ctx, brand, muted));
1172
1216
  break;
1173
1217
  }
1174
1218
  case "countdown": {
1175
- const cd = buildCountdownElement(el, fg, muted);
1219
+ const cd = buildCountdownElement(el2, fg, muted);
1176
1220
  if (cd) container.appendChild(cd);
1177
1221
  break;
1178
1222
  }
1179
1223
  case "rating_input": {
1180
- container.appendChild(buildRatingInputElement(el, ctx, fg));
1224
+ container.appendChild(buildRatingInputElement(el2, ctx, fg));
1181
1225
  break;
1182
1226
  }
1183
1227
  case "poll": {
1184
- const p = buildPollElement(el, ctx, fg, brand);
1228
+ const p = buildPollElement(el2, ctx, fg, brand);
1185
1229
  if (p) container.appendChild(p);
1186
1230
  break;
1187
1231
  }
1188
1232
  case "nps": {
1189
- container.appendChild(buildNpsElement(el, ctx, fg, brand, muted));
1233
+ container.appendChild(buildNpsElement(el2, ctx, fg, brand, muted));
1190
1234
  break;
1191
1235
  }
1192
1236
  case "group": {
1193
1237
  const g = document.createElement("div");
1194
- const gdir = el.layout === "row" ? "row" : "column";
1195
- 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;"}`;
1196
- 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 || []);
1197
1241
  container.appendChild(g);
1198
1242
  break;
1199
1243
  }
1200
1244
  case "cta": {
1201
- const label = el.text || card.cta_text || (inline ? "Add" : "Shop");
1245
+ const label = el2.text || card.cta_text || (inline ? "Add" : "Shop");
1202
1246
  const cta = document.createElement("button");
1203
1247
  cta.className = "aegis-cta";
1204
1248
  cta.textContent = label;
1205
- const overlayOnImage = !!el.overlay && !!imageWrap;
1249
+ const overlayOnImage = !!el2.overlay && !!imageWrap;
1206
1250
  if (overlayOnImage) {
1207
- 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);`;
1208
1252
  } else {
1209
- 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;`;
1210
1254
  }
1211
1255
  cta.addEventListener("click", (e) => {
1256
+ var _a;
1212
1257
  e.stopPropagation();
1213
- trackEvent(campaign.id, "clicked", { stepId: el.id || "add" });
1214
- if (inline) {
1215
- const m2 = meta;
1216
- if (ctx.dispatchAction) {
1217
- ctx.dispatchAction({
1218
- type: "add_to_cart",
1219
- productId: m2.product_id,
1220
- sku: m2.sku,
1221
- // Platform-native id (catalog-sync C.2) — plugins use this for a
1222
- // native add; undefined today → graceful product-search fallback.
1223
- platformProductId: m2.platform_product_id,
1224
- title: card.title,
1225
- price: m2.price ?? card.body,
1226
- imageUrl: card.image_url,
1227
- url: m2.platform_url || card.cta_url,
1228
- qty: 1
1229
- });
1230
- } else if (typeof window !== "undefined") {
1231
- window.dispatchEvent(
1232
- new CustomEvent("aegis:add-to-cart", {
1233
- detail: {
1234
- campaign_id: campaign.id,
1235
- product_id: m2.product_id,
1236
- sku: m2.sku,
1237
- title: card.title,
1238
- price: m2.price ?? card.body,
1239
- image_url: card.image_url,
1240
- cta_url: card.cta_url
1241
- }
1242
- })
1243
- );
1244
- }
1245
- return;
1246
- }
1247
- if (card.cta_url) {
1248
- const safe = sanitizeUrl(card.cta_url);
1249
- if (safe) ctx.navigate ? ctx.navigate(safe) : window.location.href = safe;
1250
- }
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));
1251
1260
  });
1252
1261
  (overlayOnImage ? imageWrap : container).appendChild(cta);
1253
1262
  break;
@@ -1257,37 +1266,20 @@ function renderCardFromTemplate(card, opts) {
1257
1266
  };
1258
1267
  paintInto(tile, elements);
1259
1268
  const open = (e) => {
1269
+ var _a;
1260
1270
  e.stopPropagation();
1261
1271
  trackEvent(campaign.id, "clicked");
1262
- if (inline) {
1263
- const bm = meta;
1264
- if (ctx.dispatchAction) {
1265
- ctx.dispatchAction({
1266
- type: "open_product",
1267
- productId: bm.product_id,
1268
- sku: bm.sku,
1269
- url: bm.platform_url || card.cta_url,
1270
- platformProductId: bm.platform_product_id,
1271
- platformUrl: bm.platform_url
1272
- });
1273
- } else if (typeof window !== "undefined") {
1274
- window.dispatchEvent(
1275
- new CustomEvent("aegis:open-product", {
1276
- detail: {
1277
- campaign_id: campaign.id,
1278
- product_id: bm.product_id,
1279
- sku: bm.sku,
1280
- cta_url: card.cta_url
1281
- }
1282
- })
1283
- );
1284
- }
1285
- return;
1286
- }
1287
- if (card.cta_url) {
1288
- const safe = sanitizeUrl(card.cta_url);
1289
- if (safe) ctx.navigate ? ctx.navigate(safe) : window.location.href = safe;
1290
- }
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);
1291
1283
  };
1292
1284
  if (inline || card.cta_url) tile.addEventListener("click", open);
1293
1285
  if (frame.celebrate_on_show === true && ctx.playReaction) {
@@ -1320,6 +1312,7 @@ function renderCarouselCards(ctx) {
1320
1312
  }
1321
1313
  const autoplay = Number(ic.autoplay_ms) || 0;
1322
1314
  const loop = ic.loop !== false;
1315
+ const layout = ic.rec_layout || "row";
1323
1316
  addAnimationStyles();
1324
1317
  ensureInAppFont();
1325
1318
  const bg = "#ffffff";
@@ -1367,20 +1360,26 @@ function renderCarouselCards(ctx) {
1367
1360
  header.appendChild(closeBtn);
1368
1361
  card.appendChild(header);
1369
1362
  const track = document.createElement("div");
1370
- track.style.cssText = `
1371
- display: flex; gap: 10px; overflow-x: auto; scroll-snap-type: x proximity;
1372
- scroll-padding-left: 0; scrollbar-width: none; -ms-overflow-style: none;
1373
- /* room so overflow-x: auto doesn't clip the card drop-shadow */
1374
- padding: 8px 6px 16px; margin: -8px -6px -6px; -webkit-overflow-scrolling: touch;
1375
- `;
1376
- 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
+ }
1377
1376
  const template = resolveGlobalTemplate(ic);
1378
1377
  cards.forEach((c, i) => {
1379
1378
  const tile = renderCardFromTemplate(c, {
1380
1379
  template,
1381
1380
  ctx,
1382
1381
  action: "navigate",
1383
- layout: "row",
1382
+ layout,
1384
1383
  layoutAccent: accent
1385
1384
  });
1386
1385
  tile.setAttribute("data-card-index", String(i));
@@ -1390,6 +1389,12 @@ function renderCarouselCards(ctx) {
1390
1389
  track.appendChild(tile);
1391
1390
  });
1392
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
+ }
1393
1398
  const dots = document.createElement("div");
1394
1399
  dots.style.cssText = "display: flex; justify-content: center; gap: 6px; margin-top: 10px;";
1395
1400
  const dotEls = [];
@@ -1419,9 +1424,9 @@ function renderCarouselCards(ctx) {
1419
1424
  const tiles = track.querySelectorAll("[data-card-index]");
1420
1425
  const goto = (idx) => {
1421
1426
  activeIdx = (idx % cards.length + cards.length) % cards.length;
1422
- const el = tiles[activeIdx];
1423
- if (el) {
1424
- 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;
1425
1430
  track.scrollTo({ left: track.scrollLeft + delta, behavior: "smooth" });
1426
1431
  setActive(activeIdx);
1427
1432
  }
@@ -2038,6 +2043,10 @@ function renderProductRecommendation(ctx) {
2038
2043
  display: flex; gap: 10px; overflow-x: auto;
2039
2044
  scroll-snap-type: x mandatory; padding: 14px 2px 18px;
2040
2045
  `;
2046
+ } else if (layout === "list") {
2047
+ grid.style.cssText = `
2048
+ display: flex; flex-direction: column; gap: 10px; padding: 14px 2px 6px;
2049
+ `;
2041
2050
  } else {
2042
2051
  grid.style.cssText = `
2043
2052
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;
@@ -2094,9 +2103,11 @@ function renderProductRecommendationInline(ctx, target) {
2094
2103
  section.appendChild(body);
2095
2104
  }
2096
2105
  const grid = document.createElement("div");
2097
- const isRow = layout !== "grid";
2106
+ const isRow = layout === "row" || layout !== "grid" && layout !== "list";
2098
2107
  if (layout === "grid") {
2099
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;";
2100
2111
  } else {
2101
2112
  grid.style.cssText = `
2102
2113
  display: flex; gap: 10px; overflow-x: auto;
@@ -2639,10 +2650,10 @@ function brandAccent(ctx) {
2639
2650
  function injectGameStyles() {
2640
2651
  if (typeof document === "undefined" || document.getElementById("aegis-game-styles")) return;
2641
2652
  if (typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
2642
- const el = document.createElement("style");
2643
- el.id = "aegis-game-styles";
2644
- 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}}";
2645
- 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);
2646
2657
  }
2647
2658
  function openGameModal(ctx, className, maxWidth = 360) {
2648
2659
  const { campaign, trackEvent, addAnimationStyles } = ctx;
@@ -4150,8 +4161,8 @@ class AegisChat {
4150
4161
  }
4151
4162
  if (msg.interactive && Array.isArray(msg.interactive)) {
4152
4163
  for (const inter of msg.interactive) {
4153
- const el = this.renderInteractive(inter);
4154
- if (el) row.appendChild(el);
4164
+ const el2 = this.renderInteractive(inter);
4165
+ if (el2) row.appendChild(el2);
4155
4166
  }
4156
4167
  }
4157
4168
  this.thread.appendChild(row);
@@ -5015,6 +5026,432 @@ function disposeActiveWebChat() {
5015
5026
  activeInstance == null ? void 0 : activeInstance.destroy();
5016
5027
  activeInstance = null;
5017
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 star(filled, size) {
5037
+ const svg = document.createElementNS(SVG_NS, "svg");
5038
+ svg.setAttribute("viewBox", "0 0 24 24");
5039
+ svg.setAttribute("width", String(size));
5040
+ svg.setAttribute("height", String(size));
5041
+ const p = document.createElementNS(SVG_NS, "path");
5042
+ p.setAttribute(
5043
+ "d",
5044
+ "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"
5045
+ );
5046
+ p.setAttribute("fill", filled ? "#f59e0b" : "#e5e7eb");
5047
+ svg.appendChild(p);
5048
+ return svg;
5049
+ }
5050
+ function stars(rating, size = 15) {
5051
+ const wrap = el("span", "display:inline-flex;gap:2px;vertical-align:middle;");
5052
+ wrap.setAttribute("aria-label", `${rating} out of 5`);
5053
+ for (let i = 1; i <= 5; i++) wrap.appendChild(star(i <= rating, size));
5054
+ return wrap;
5055
+ }
5056
+ function verifiedBadge() {
5057
+ const b = el(
5058
+ "span",
5059
+ "display:inline-flex;align-items:center;gap:3px;font-size:12px;font-weight:600;color:#059669;"
5060
+ );
5061
+ const svg = document.createElementNS(SVG_NS, "svg");
5062
+ svg.setAttribute("viewBox", "0 0 24 24");
5063
+ svg.setAttribute("width", "13");
5064
+ svg.setAttribute("height", "13");
5065
+ const p = document.createElementNS(SVG_NS, "path");
5066
+ p.setAttribute("d", "M9 12l2 2 4-4M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z");
5067
+ p.setAttribute("fill", "none");
5068
+ p.setAttribute("stroke", "#059669");
5069
+ p.setAttribute("stroke-width", "2");
5070
+ p.setAttribute("stroke-linecap", "round");
5071
+ p.setAttribute("stroke-linejoin", "round");
5072
+ svg.appendChild(p);
5073
+ b.appendChild(svg);
5074
+ b.appendChild(el("span", "", "Verified purchase"));
5075
+ return b;
5076
+ }
5077
+ function avatar(name, accent) {
5078
+ const initial = (name.trim()[0] || "?").toUpperCase();
5079
+ const a = el(
5080
+ "span",
5081
+ `flex:0 0 auto;width:34px;height:34px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;color:${accent};background:${accent}1a;`,
5082
+ initial
5083
+ );
5084
+ return a;
5085
+ }
5086
+ function thumbBtn(up, count, reviewId, creds) {
5087
+ const btn = el(
5088
+ "button",
5089
+ "display:inline-flex;align-items:center;gap:5px;font-size:13px;color:#6b7280;background:none;border:none;cursor:pointer;padding:4px 6px;border-radius:8px;font-family:inherit;"
5090
+ );
5091
+ const svg = document.createElementNS(SVG_NS, "svg");
5092
+ svg.setAttribute("viewBox", "0 0 24 24");
5093
+ svg.setAttribute("width", "15");
5094
+ svg.setAttribute("height", "15");
5095
+ if (!up) svg.style.cssText = "transform:rotate(180deg);";
5096
+ const p = document.createElementNS(SVG_NS, "path");
5097
+ p.setAttribute(
5098
+ "d",
5099
+ "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"
5100
+ );
5101
+ p.setAttribute("fill", "currentColor");
5102
+ svg.appendChild(p);
5103
+ btn.appendChild(svg);
5104
+ const c = el("span", "", String(count));
5105
+ btn.appendChild(c);
5106
+ const key = `aegis_rv_voted_${reviewId}`;
5107
+ let voted = false;
5108
+ try {
5109
+ voted = typeof localStorage !== "undefined" && localStorage.getItem(key) != null;
5110
+ } catch {
5111
+ }
5112
+ if (voted) btn.style.opacity = "0.5";
5113
+ btn.addEventListener("mouseenter", () => {
5114
+ if (!voted) btn.style.color = "#111827";
5115
+ });
5116
+ btn.addEventListener("mouseleave", () => {
5117
+ btn.style.color = "#6b7280";
5118
+ });
5119
+ btn.addEventListener("click", () => {
5120
+ try {
5121
+ if (localStorage.getItem(key) != null) return;
5122
+ localStorage.setItem(key, up ? "up" : "down");
5123
+ } catch {
5124
+ }
5125
+ voted = true;
5126
+ c.textContent = String(count + 1);
5127
+ btn.style.opacity = "0.5";
5128
+ const url = new URL(`/v1/reviews/${reviewId}/helpful`, creds.apiHost);
5129
+ void fetch(url.toString(), {
5130
+ method: "POST",
5131
+ headers: { "X-Aegis-Write-Key": creds.writeKey, "Content-Type": "application/json" },
5132
+ body: JSON.stringify({ direction: up ? "up" : "down" })
5133
+ }).catch(() => {
5134
+ });
5135
+ });
5136
+ return btn;
5137
+ }
5138
+ function injectJsonLd(target, data, productId) {
5139
+ if (!data.aggregate.count || data.aggregate.avg_rating == null) return;
5140
+ const ld = {
5141
+ "@context": "https://schema.org",
5142
+ "@type": "Product",
5143
+ ...productId ? { sku: productId } : {},
5144
+ aggregateRating: {
5145
+ "@type": "AggregateRating",
5146
+ ratingValue: data.aggregate.avg_rating,
5147
+ reviewCount: data.aggregate.count
5148
+ },
5149
+ review: data.items.slice(0, 10).map((r) => {
5150
+ var _a;
5151
+ return {
5152
+ "@type": "Review",
5153
+ reviewRating: { "@type": "Rating", ratingValue: r.rating ?? 0 },
5154
+ author: { "@type": "Person", name: ((_a = r.reviewer_snapshot) == null ? void 0 : _a.name) ?? "Customer" },
5155
+ ...r.review_text ? { reviewBody: r.review_text } : {}
5156
+ };
5157
+ })
5158
+ };
5159
+ const script = document.createElement("script");
5160
+ script.type = "application/ld+json";
5161
+ script.textContent = JSON.stringify(ld);
5162
+ target.appendChild(script);
5163
+ }
5164
+ const SORTS = [
5165
+ { key: "recent", label: "Most recent" },
5166
+ { key: "helpful", label: "Most helpful" },
5167
+ { key: "rating", label: "Highest rated" }
5168
+ ];
5169
+ function renderReviewsInline(ctx, target, creds) {
5170
+ if (typeof document === "undefined" || !(creds == null ? void 0 : creds.apiHost) || !(creds == null ? void 0 : creds.writeKey)) return false;
5171
+ ensureInAppFont();
5172
+ const ic = ctx.campaign.interactive_config || {};
5173
+ const accent = ctx.sanitizeColor(ctx.campaign.background_color || "#4169e1");
5174
+ const brand = String(ic.brand_name || "").trim() || "The store";
5175
+ const productId = target.getAttribute("data-product-id") || ctx.campaign.product_id || null;
5176
+ const state = { sort: "recent", withPhotos: false };
5177
+ target.innerHTML = "";
5178
+ target.appendChild(el("div", `font-family:${IN_APP_FONT_STACK};font-size:13px;color:#9ca3af;padding:10px 0;`, "Loading reviews…"));
5179
+ function buildUrl() {
5180
+ const url = new URL("/v1/reviews/public", creds.apiHost);
5181
+ if (productId) url.searchParams.set("product_id", productId);
5182
+ url.searchParams.set("sort", state.sort);
5183
+ if (state.withPhotos) url.searchParams.set("with_photos", "true");
5184
+ url.searchParams.set("limit", "20");
5185
+ return url.toString();
5186
+ }
5187
+ function fetchReviews() {
5188
+ return fetch(buildUrl(), { method: "GET", headers: { "X-Aegis-Write-Key": creds.writeKey } }).then((r) => r.ok ? r.json() : null).catch(() => null);
5189
+ }
5190
+ let listContainer = null;
5191
+ function reviewCard(r) {
5192
+ var _a, _b, _c;
5193
+ const li = el("li", "padding:22px 0;border-top:1px solid #f0f1f3;");
5194
+ const head = el("div", "display:flex;align-items:center;gap:10px;");
5195
+ head.appendChild(avatar(((_a = r.reviewer_snapshot) == null ? void 0 : _a.name) ?? "Customer", accent));
5196
+ const idn = el("div", "display:flex;flex-direction:column;gap:2px;min-width:0;");
5197
+ const nameRow = el("div", "display:flex;flex-wrap:wrap;align-items:center;gap:8px;");
5198
+ nameRow.appendChild(el("span", "font-size:15px;font-weight:700;color:#111827;", ((_b = r.reviewer_snapshot) == null ? void 0 : _b.name) ?? "Customer"));
5199
+ if (r.verified_purchase) nameRow.appendChild(verifiedBadge());
5200
+ idn.appendChild(nameRow);
5201
+ idn.appendChild(stars(r.rating ?? 0, 14));
5202
+ head.appendChild(idn);
5203
+ if (r.created_at) {
5204
+ const d = new Date(r.created_at);
5205
+ if (!Number.isNaN(d.getTime())) {
5206
+ head.appendChild(el(
5207
+ "span",
5208
+ "margin-left:auto;font-size:12px;color:#9ca3af;white-space:nowrap;",
5209
+ d.toLocaleDateString(void 0, { day: "numeric", month: "short", year: "numeric" })
5210
+ ));
5211
+ }
5212
+ }
5213
+ li.appendChild(head);
5214
+ if (r.review_text) {
5215
+ li.appendChild(el("p", "margin:12px 0 0;font-size:15px;line-height:1.6;color:#374151;", r.review_text));
5216
+ }
5217
+ if (r.media && r.media.length > 0) {
5218
+ const gallery = el("div", "display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;");
5219
+ for (const m2 of r.media.slice(0, 6)) {
5220
+ const safe = ctx.sanitizeUrl(m2.url);
5221
+ if (!safe) continue;
5222
+ const img = document.createElement("img");
5223
+ img.src = safe;
5224
+ img.loading = "lazy";
5225
+ img.alt = "";
5226
+ img.style.cssText = "width:72px;height:72px;object-fit:cover;border-radius:12px;border:1px solid #f0f1f3;";
5227
+ gallery.appendChild(img);
5228
+ }
5229
+ if (gallery.childElementCount) li.appendChild(gallery);
5230
+ }
5231
+ const foot = el("div", "display:flex;align-items:center;gap:6px;margin-top:12px;");
5232
+ foot.appendChild(el("span", "font-size:13px;color:#9ca3af;margin-right:2px;", "Was this helpful?"));
5233
+ foot.appendChild(thumbBtn(true, r.helpful_count ?? 0, r.id, creds));
5234
+ foot.appendChild(thumbBtn(false, r.not_helpful_count ?? 0, r.id, creds));
5235
+ li.appendChild(foot);
5236
+ if ((_c = r.operator_reply) == null ? void 0 : _c.text) {
5237
+ const reply = el(
5238
+ "div",
5239
+ `margin-top:14px;margin-left:44px;border-left:3px solid ${accent};background:${accent}0d;border-radius:0 12px 12px 0;padding:12px 14px;`
5240
+ );
5241
+ const rlabel = el("div", "display:flex;align-items:center;gap:6px;margin-bottom:5px;");
5242
+ rlabel.appendChild(el("span", `width:7px;height:7px;border-radius:999px;background:${accent};display:inline-block;`));
5243
+ rlabel.appendChild(el("span", `font-size:12px;font-weight:700;color:${accent};`, brand));
5244
+ rlabel.appendChild(el("span", "font-size:12px;color:#9ca3af;", "· replied"));
5245
+ reply.appendChild(rlabel);
5246
+ reply.appendChild(el("p", "margin:0;font-size:14px;line-height:1.55;color:#4b5563;", r.operator_reply.text));
5247
+ li.appendChild(reply);
5248
+ }
5249
+ return li;
5250
+ }
5251
+ function renderList(items) {
5252
+ if (!listContainer) return;
5253
+ listContainer.innerHTML = "";
5254
+ if (items.length === 0) {
5255
+ listContainer.appendChild(el("p", "padding:20px 0;font-size:14px;color:#9ca3af;text-align:center;", "No reviews match this filter."));
5256
+ return;
5257
+ }
5258
+ const ul = el("ul", "list-style:none;margin:0;padding:0;");
5259
+ for (const r of items) ul.appendChild(reviewCard(r));
5260
+ listContainer.appendChild(ul);
5261
+ }
5262
+ function pill(label, active, onClick) {
5263
+ const b = el(
5264
+ "button",
5265
+ `font-size:13px;font-weight:600;padding:7px 16px;border-radius:999px;cursor:pointer;font-family:inherit;` + (active ? `background:${accent};color:#fff;border:1px solid ${accent};` : "background:#fff;color:#374151;border:1px solid #e5e7eb;"),
5266
+ label
5267
+ );
5268
+ b.addEventListener("click", onClick);
5269
+ return b;
5270
+ }
5271
+ function controls(rerender) {
5272
+ const bar = el("div", "display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:18px 0 4px;");
5273
+ const sel = document.createElement("select");
5274
+ sel.style.cssText = `font-size:13px;font-weight:600;padding:7px 30px 7px 14px;border:1px solid #e5e7eb;border-radius:999px;background:#fff;color:#374151;cursor:pointer;font-family:inherit;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center;`;
5275
+ for (const s of SORTS) {
5276
+ const o = document.createElement("option");
5277
+ o.value = s.key;
5278
+ o.textContent = s.label;
5279
+ sel.appendChild(o);
5280
+ }
5281
+ sel.value = state.sort;
5282
+ sel.addEventListener("change", () => {
5283
+ state.sort = sel.value;
5284
+ rerender();
5285
+ });
5286
+ bar.appendChild(sel);
5287
+ let photosBtn = null;
5288
+ photosBtn = pill("With photos", state.withPhotos, () => {
5289
+ state.withPhotos = !state.withPhotos;
5290
+ if (photosBtn) {
5291
+ photosBtn.style.cssText = `font-size:13px;font-weight:600;padding:7px 16px;border-radius:999px;cursor:pointer;font-family:inherit;` + (state.withPhotos ? `background:${accent};color:#fff;border:1px solid ${accent};` : "background:#fff;color:#374151;border:1px solid #e5e7eb;");
5292
+ }
5293
+ rerender();
5294
+ });
5295
+ bar.appendChild(photosBtn);
5296
+ return bar;
5297
+ }
5298
+ function distributionPanel(dist, total) {
5299
+ const box = el("div", "flex:1 1 260px;max-width:340px;display:flex;flex-direction:column;gap:7px;");
5300
+ for (let r = 5; r >= 1; r--) {
5301
+ const n = dist[String(r)] ?? 0;
5302
+ const pct = total > 0 ? Math.round(n / total * 100) : 0;
5303
+ const row = el("div", "display:flex;align-items:center;gap:10px;font-size:13px;color:#6b7280;");
5304
+ const lbl = el("span", "width:30px;display:inline-flex;align-items:center;gap:3px;color:#374151;font-weight:600;", String(r));
5305
+ lbl.appendChild(star(true, 11));
5306
+ row.appendChild(lbl);
5307
+ const track = el("div", "flex:1;height:8px;background:#f0f1f3;border-radius:999px;overflow:hidden;");
5308
+ track.appendChild(el("div", `width:${pct}%;height:100%;background:${accent};border-radius:999px;`));
5309
+ row.appendChild(track);
5310
+ row.appendChild(el("span", "width:24px;text-align:right;font-variant-numeric:tabular-nums;", String(n)));
5311
+ box.appendChild(row);
5312
+ }
5313
+ return box;
5314
+ }
5315
+ function aiSummaryCard(summary) {
5316
+ const card = el(
5317
+ "div",
5318
+ "position:relative;border-radius:18px;padding:18px 20px;margin-bottom:24px;background:linear-gradient(135deg,#faf5ff 0%,#f5f3ff 100%);border:1px solid #ede9fe;"
5319
+ );
5320
+ const head = el("div", "display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;");
5321
+ const left = el("div", "display:flex;align-items:center;gap:7px;");
5322
+ const sp = document.createElementNS(SVG_NS, "svg");
5323
+ sp.setAttribute("viewBox", "0 0 24 24");
5324
+ sp.setAttribute("width", "16");
5325
+ sp.setAttribute("height", "16");
5326
+ const spp = document.createElementNS(SVG_NS, "path");
5327
+ spp.setAttribute("d", "M12 3l1.6 4.4L18 9l-4.4 1.6L12 15l-1.6-4.4L6 9l4.4-1.6L12 3z");
5328
+ spp.setAttribute("fill", "#7c3aed");
5329
+ sp.appendChild(spp);
5330
+ left.appendChild(sp);
5331
+ left.appendChild(el("span", "font-size:12px;font-weight:800;letter-spacing:.07em;color:#6d28d9;text-transform:uppercase;", "What customers say"));
5332
+ head.appendChild(left);
5333
+ head.appendChild(el("span", "font-size:11px;font-weight:600;color:#a78bda;", "AI-generated"));
5334
+ card.appendChild(head);
5335
+ card.appendChild(el("p", "margin:0;font-size:15px;line-height:1.6;color:#3b2a63;", summary.text));
5336
+ card.appendChild(el("p", "margin:8px 0 0;font-size:12px;color:#a78bda;text-align:right;", `from ${summary.review_count} reviews`));
5337
+ return card;
5338
+ }
5339
+ function renderWall(data) {
5340
+ target.innerHTML = "";
5341
+ const root = el(
5342
+ "section",
5343
+ `font-family:${IN_APP_FONT_STACK};color:#111827;max-width:860px;margin:0 auto;-webkit-font-smoothing:antialiased;`
5344
+ );
5345
+ root.setAttribute("data-aegis-reviews", "true");
5346
+ const summary = data.aggregate.ai_summary;
5347
+ if (summary && summary.text) root.appendChild(aiSummaryCard(summary));
5348
+ if (data.aggregate.count === 0) {
5349
+ root.appendChild(el("p", "font-size:15px;color:#9ca3af;padding:6px 0;", "No reviews yet — be the first to share your experience."));
5350
+ target.appendChild(root);
5351
+ return;
5352
+ }
5353
+ const header = el("div", "display:flex;flex-wrap:wrap;align-items:center;gap:24px 40px;padding-bottom:4px;");
5354
+ const ratingBlock = el("div", "display:flex;flex-direction:column;gap:4px;");
5355
+ const topRow = el("div", "display:flex;align-items:center;gap:12px;");
5356
+ const avg = data.aggregate.avg_rating ?? 0;
5357
+ topRow.appendChild(stars(Math.round(avg), 20));
5358
+ topRow.appendChild(el("span", "font-size:32px;font-weight:800;line-height:1;letter-spacing:-0.02em;", avg ? avg.toFixed(1) : "—"));
5359
+ ratingBlock.appendChild(topRow);
5360
+ ratingBlock.appendChild(el("span", "font-size:13px;color:#6b7280;", `${data.aggregate.count} review${data.aggregate.count === 1 ? "" : "s"} · 100% verified`));
5361
+ header.appendChild(ratingBlock);
5362
+ header.appendChild(distributionPanel(data.aggregate.distribution, data.aggregate.count));
5363
+ root.appendChild(header);
5364
+ root.appendChild(controls(() => {
5365
+ void fetchReviews().then((d) => renderList((d == null ? void 0 : d.items) ?? []));
5366
+ }));
5367
+ listContainer = el("div", "margin-top:2px;");
5368
+ root.appendChild(listContainer);
5369
+ renderList(data.items);
5370
+ target.appendChild(root);
5371
+ injectJsonLd(target, data, productId);
5372
+ ctx.trackEvent(ctx.campaign.id, "impression");
5373
+ }
5374
+ const compact = ic.display_style === "compact";
5375
+ function renderCompact(data) {
5376
+ var _a;
5377
+ target.innerHTML = "";
5378
+ if (data.aggregate.count === 0) return;
5379
+ const root = el(
5380
+ "section",
5381
+ `font-family:${IN_APP_FONT_STACK};color:#111827;max-width:860px;margin:0 auto;border:1px solid #f0f1f3;border-radius:16px;padding:16px 18px;`
5382
+ );
5383
+ const line = el("div", "display:flex;align-items:center;gap:8px;");
5384
+ const avg = data.aggregate.avg_rating ?? 0;
5385
+ line.appendChild(stars(Math.round(avg), 16));
5386
+ line.appendChild(el("span", "font-size:16px;font-weight:700;", avg ? avg.toFixed(1) : "—"));
5387
+ line.appendChild(el("span", "font-size:13px;color:#6b7280;", `(${data.aggregate.count})`));
5388
+ line.appendChild(verifiedBadge());
5389
+ root.appendChild(line);
5390
+ const top = data.items.find((i) => i.review_text);
5391
+ if (top && top.review_text) {
5392
+ root.appendChild(el("p", "margin:10px 0 0;font-size:15px;color:#374151;line-height:1.5;", `“${top.review_text}”`));
5393
+ root.appendChild(el("span", "font-size:13px;color:#9ca3af;", `— ${((_a = top.reviewer_snapshot) == null ? void 0 : _a.name) ?? "Customer"}`));
5394
+ }
5395
+ const seeAll = el("button", `margin-top:12px;font-size:14px;font-weight:700;color:${accent};background:none;border:none;cursor:pointer;padding:0;font-family:inherit;`, "See all reviews →");
5396
+ seeAll.addEventListener("click", () => renderWall(data));
5397
+ root.appendChild(seeAll);
5398
+ target.appendChild(root);
5399
+ ctx.trackEvent(ctx.campaign.id, "impression");
5400
+ }
5401
+ void fetchReviews().then((data) => {
5402
+ if (!data || !Array.isArray(data.items)) {
5403
+ target.innerHTML = "";
5404
+ return;
5405
+ }
5406
+ if (compact) renderCompact(data);
5407
+ else renderWall(data);
5408
+ });
5409
+ return true;
5410
+ }
5411
+ function renderReviewsOverlay(ctx, creds) {
5412
+ if (typeof document === "undefined" || !(creds == null ? void 0 : creds.apiHost) || !(creds == null ? void 0 : creds.writeKey)) return;
5413
+ ensureInAppFont();
5414
+ const overlay = document.createElement("div");
5415
+ overlay.setAttribute("data-campaign-id", ctx.campaign.id);
5416
+ overlay.style.cssText = "position:fixed;inset:0;background:rgba(17,24,39,0.5);display:flex;align-items:flex-end;justify-content:center;z-index:99999;";
5417
+ const sheet = document.createElement("div");
5418
+ sheet.style.cssText = `background:#fff;color:#111827;max-width:680px;width:100%;max-height:84vh;overflow-y:auto;border-radius:24px 24px 0 0;padding:18px 18px 28px;font-family:${IN_APP_FONT_STACK};box-shadow:0 -16px 40px rgba(0,0,0,0.14);`;
5419
+ sheet.appendChild(el("div", "width:38px;height:4px;border-radius:999px;background:#e5e7eb;margin:0 auto 14px;"));
5420
+ const bar = el("div", "display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;");
5421
+ bar.appendChild(el("div", "font-weight:800;font-size:17px;", ctx.campaign.title || "Customer reviews"));
5422
+ const close = el("button", "background:none;border:none;cursor:pointer;padding:4px 8px;color:#6b7280;");
5423
+ const x = document.createElementNS(SVG_NS, "svg");
5424
+ x.setAttribute("viewBox", "0 0 24 24");
5425
+ x.setAttribute("width", "20");
5426
+ x.setAttribute("height", "20");
5427
+ const xp = document.createElementNS(SVG_NS, "path");
5428
+ xp.setAttribute("d", "M18 6L6 18M6 6l12 12");
5429
+ xp.setAttribute("stroke", "currentColor");
5430
+ xp.setAttribute("stroke-width", "2");
5431
+ xp.setAttribute("stroke-linecap", "round");
5432
+ x.appendChild(xp);
5433
+ close.appendChild(x);
5434
+ close.setAttribute("aria-label", "Close");
5435
+ close.addEventListener("click", () => {
5436
+ ctx.trackEvent(ctx.campaign.id, "dismissed");
5437
+ overlay.remove();
5438
+ });
5439
+ bar.appendChild(close);
5440
+ sheet.appendChild(bar);
5441
+ const body = el("div");
5442
+ const pid = ctx.campaign.product_id;
5443
+ if (pid) body.setAttribute("data-product-id", pid);
5444
+ sheet.appendChild(body);
5445
+ overlay.appendChild(sheet);
5446
+ overlay.addEventListener("click", (e) => {
5447
+ if (e.target === overlay) {
5448
+ ctx.trackEvent(ctx.campaign.id, "dismissed");
5449
+ overlay.remove();
5450
+ }
5451
+ });
5452
+ document.body.appendChild(overlay);
5453
+ renderReviewsInline(ctx, body, creds);
5454
+ }
5018
5455
  function readAnonIdFromStorage$1() {
5019
5456
  if (typeof document === "undefined") return void 0;
5020
5457
  try {
@@ -5642,8 +6079,8 @@ const _AegisInAppManager = class _AegisInAppManager {
5642
6079
  m2.addedNodes.forEach((n) => {
5643
6080
  var _a, _b;
5644
6081
  if (sawSlot || n.nodeType !== 1) return;
5645
- const el = n;
5646
- 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]"))) {
6082
+ const el2 = n;
6083
+ 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]"))) {
5647
6084
  sawSlot = true;
5648
6085
  }
5649
6086
  });
@@ -5850,13 +6287,13 @@ const _AegisInAppManager = class _AegisInAppManager {
5850
6287
  const anchor = ic == null ? void 0 : ic.anchor;
5851
6288
  const selector = anchor == null ? void 0 : anchor.element_selector;
5852
6289
  if (!selector) continue;
5853
- let el = null;
6290
+ let el2 = null;
5854
6291
  try {
5855
- el = document.querySelector(selector);
6292
+ el2 = document.querySelector(selector);
5856
6293
  } catch {
5857
- el = null;
6294
+ el2 = null;
5858
6295
  }
5859
- if (!el) {
6296
+ if (!el2) {
5860
6297
  void this.trackEvent(c.id, "anchor_unresolved", {
5861
6298
  meta: { anchor_resolved: false, anchor_selector: selector, widget_type: c.sub_type }
5862
6299
  });
@@ -5867,9 +6304,9 @@ const _AegisInAppManager = class _AegisInAppManager {
5867
6304
  if (position === "before" || position === "after") {
5868
6305
  target = document.createElement("div");
5869
6306
  target.setAttribute("data-aegis-anchor-wrapper", c.id);
5870
- (_a = el.parentNode) == null ? void 0 : _a.insertBefore(target, position === "before" ? el : el.nextSibling);
6307
+ (_a = el2.parentNode) == null ? void 0 : _a.insertBefore(target, position === "before" ? el2 : el2.nextSibling);
5871
6308
  } else {
5872
- target = el;
6309
+ target = el2;
5873
6310
  if (position === "replace") {
5874
6311
  target.querySelectorAll(":scope > [data-aegis-slot-default]").forEach((d) => d.remove());
5875
6312
  }
@@ -6026,6 +6463,12 @@ const _AegisInAppManager = class _AegisInAppManager {
6026
6463
  case "product_recommendation":
6027
6464
  rendered = renderProductRecommendationInline(this.buildRenderContext(campaign), target);
6028
6465
  break;
6466
+ case "reviews":
6467
+ rendered = renderReviewsInline(this.buildRenderContext(campaign), target, {
6468
+ apiHost: this.apiHost,
6469
+ writeKey: this.writeKey
6470
+ });
6471
+ break;
6029
6472
  case "quiz":
6030
6473
  rendered = this.renderQuizSlot(campaign, ic, bg, text, target);
6031
6474
  if (!rendered) {
@@ -7078,17 +7521,27 @@ const _AegisInAppManager = class _AegisInAppManager {
7078
7521
  h.textContent = campaign.title;
7079
7522
  card.appendChild(h);
7080
7523
  }
7524
+ const layout = ic.rec_layout || "row";
7081
7525
  const strip = document.createElement("div");
7082
- strip.style.cssText = `display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory; padding: ${st.padY ?? 12}px ${st.padX ?? 12}px;`;
7526
+ const pad = `${st.padY ?? 12}px ${st.padX ?? 12}px`;
7527
+ if (layout === "grid") {
7528
+ strip.style.cssText = `display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; padding: ${pad};`;
7529
+ } else if (layout === "list") {
7530
+ strip.style.cssText = `display: flex; flex-direction: column; gap: 8px; padding: ${pad};`;
7531
+ } else {
7532
+ strip.style.cssText = `display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; scrollbar-width: none; padding: ${pad};`;
7533
+ }
7083
7534
  const template = resolveGlobalTemplate(ic);
7084
7535
  const ctx = this.buildRenderContext(campaign);
7085
7536
  const accent = this.sanitizeColor(campaign.background_color || text);
7086
7537
  cards.forEach((c, i) => {
7538
+ const meta = c.metadata || {};
7539
+ const linked = !!(meta.product_id || meta.sku);
7087
7540
  const tile = renderCardFromTemplate(c, {
7088
7541
  template,
7089
7542
  ctx,
7090
- action: "navigate",
7091
- layout: "row",
7543
+ action: linked ? "cart" : "navigate",
7544
+ layout,
7092
7545
  layoutAccent: accent
7093
7546
  });
7094
7547
  tile.setAttribute("data-card-index", String(i));
@@ -7096,6 +7549,43 @@ const _AegisInAppManager = class _AegisInAppManager {
7096
7549
  });
7097
7550
  card.appendChild(strip);
7098
7551
  target.appendChild(card);
7552
+ const autoplayMs = Number(ic.autoplay_ms) || 0;
7553
+ if (layout === "row" && autoplayMs > 0 && cards.length > 1) {
7554
+ const loop = ic.loop !== false;
7555
+ const interval = Math.max(autoplayMs, 600);
7556
+ let idx = 0;
7557
+ let timer = null;
7558
+ const stop = () => {
7559
+ if (timer) {
7560
+ clearInterval(timer);
7561
+ timer = null;
7562
+ }
7563
+ };
7564
+ timer = setInterval(() => {
7565
+ if (!document.body.contains(strip)) {
7566
+ stop();
7567
+ return;
7568
+ }
7569
+ if (strip.scrollWidth <= strip.clientWidth + 4) return;
7570
+ const tiles = Array.from(strip.children);
7571
+ idx += 1;
7572
+ if (idx >= tiles.length) {
7573
+ if (!loop) {
7574
+ stop();
7575
+ return;
7576
+ }
7577
+ idx = 0;
7578
+ }
7579
+ const el2 = tiles[idx];
7580
+ if (el2) {
7581
+ const delta = el2.getBoundingClientRect().left - strip.getBoundingClientRect().left;
7582
+ strip.scrollTo({ left: strip.scrollLeft + delta, behavior: "smooth" });
7583
+ }
7584
+ }, interval);
7585
+ ["pointerdown", "wheel", "touchstart"].forEach(
7586
+ (evt) => strip.addEventListener(evt, stop, { passive: true })
7587
+ );
7588
+ }
7099
7589
  return true;
7100
7590
  }
7101
7591
  /** Built-in embedded COUNTDOWN — live H:M:S digits that pulse red in the
@@ -7118,10 +7608,10 @@ const _AegisInAppManager = class _AegisInAppManager {
7118
7608
  const digits = document.createElement("div");
7119
7609
  digits.style.cssText = "display: flex; gap: 6px; justify-content: center;";
7120
7610
  const mkDigit = () => {
7121
- const el = document.createElement("span");
7122
- 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;`;
7123
- el.textContent = "00";
7124
- return el;
7611
+ const el2 = document.createElement("span");
7612
+ 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;`;
7613
+ el2.textContent = "00";
7614
+ return el2;
7125
7615
  };
7126
7616
  const hEl = mkDigit();
7127
7617
  const mEl = mkDigit();
@@ -7151,9 +7641,9 @@ const _AegisInAppManager = class _AegisInAppManager {
7151
7641
  sEl.textContent = String(s).padStart(2, "0");
7152
7642
  if (diff > 0 && diff <= 6e4 && !urgent) {
7153
7643
  urgent = true;
7154
- [hEl, mEl, sEl].forEach((el) => {
7155
- el.style.boxShadow = "0 0 0 2px #ef4444";
7156
- el.style.animation = "aegisUrgencyPulse 1s ease-in-out infinite";
7644
+ [hEl, mEl, sEl].forEach((el2) => {
7645
+ el2.style.boxShadow = "0 0 0 2px #ef4444";
7646
+ el2.style.animation = "aegisUrgencyPulse 1s ease-in-out infinite";
7157
7647
  });
7158
7648
  }
7159
7649
  if (diff > 0) requestAnimationFrame(tick);
@@ -7695,9 +8185,9 @@ const _AegisInAppManager = class _AegisInAppManager {
7695
8185
  */
7696
8186
  _flattenSlotCard(container) {
7697
8187
  const cards = container.matches("[data-campaign-id]") ? [container] : Array.from(container.querySelectorAll("[data-campaign-id]"));
7698
- for (const el of cards) {
7699
- el.style.borderRadius = "0";
7700
- el.style.boxShadow = "none";
8188
+ for (const el2 of cards) {
8189
+ el2.style.borderRadius = "0";
8190
+ el2.style.boxShadow = "none";
7701
8191
  }
7702
8192
  }
7703
8193
  /**
@@ -7783,6 +8273,15 @@ const _AegisInAppManager = class _AegisInAppManager {
7783
8273
  this.emit("campaign-shown", campaign);
7784
8274
  return;
7785
8275
  }
8276
+ if (campaign.sub_type === "reviews") {
8277
+ renderReviewsOverlay(this.buildRenderContext(campaign), {
8278
+ apiHost: this.apiHost,
8279
+ writeKey: this.writeKey
8280
+ });
8281
+ this.trackEvent(campaign.id, "impression");
8282
+ this.emit("campaign-shown", campaign);
8283
+ return;
8284
+ }
7786
8285
  {
7787
8286
  const ic2 = campaign.interactive_config || {};
7788
8287
  const multiCard2 = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
@@ -7910,7 +8409,7 @@ const _AegisInAppManager = class _AegisInAppManager {
7910
8409
  navigate: (url, buttonId) => this.navigateToCampaignAction(campaign, url, buttonId),
7911
8410
  dispatchAction: (action) => this.dispatchAction(action, campaign.id),
7912
8411
  submitResponse: (responseType, payload) => this.submitWidgetResponse(campaign, responseType, payload),
7913
- playReaction: (el, intent, lottieFile, scope) => this._playReaction(el, intent, lottieFile, campaign.interactive_config, scope),
8412
+ playReaction: (el2, intent, lottieFile, scope) => this._playReaction(el2, intent, lottieFile, campaign.interactive_config, scope),
7914
8413
  haptic: (pattern) => {
7915
8414
  try {
7916
8415
  if (typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
@@ -8167,9 +8666,9 @@ const _AegisInAppManager = class _AegisInAppManager {
8167
8666
  if (cells.s) cells.s.textContent = pad(s);
8168
8667
  if (!urgent && diff > 0 && diff <= 6e4) {
8169
8668
  urgent = true;
8170
- Object.values(cells).forEach((el) => {
8171
- if (el) {
8172
- el.style.boxShadow = "0 0 0 2px #ef4444";
8669
+ Object.values(cells).forEach((el2) => {
8670
+ if (el2) {
8671
+ el2.style.boxShadow = "0 0 0 2px #ef4444";
8173
8672
  }
8174
8673
  });
8175
8674
  digits.style.animation = "aegisCountdownPulse 1.1s ease-in-out infinite";
@@ -8698,58 +9197,21 @@ const _AegisInAppManager = class _AegisInAppManager {
8698
9197
  * 3. if STILL unhandled → canonical fallback (navigate to the product/cart
8699
9198
  * page) so a CTA is never a dead no-op (the rail-"Add" 3rd-party gap).
8700
9199
  * Both add-to-cart paths (card "Add" + the `aegis://cart-add` verb) funnel here,
8701
- * so they're one behaviour with one legacy event (`aegis:add-to-cart`).
9200
+ * so they're one behaviour delivered to the host's single `onAction` handler.
9201
+ * (Phase E.2 hard cutover — the legacy `aegis:*` DOM events were removed.)
8702
9202
  */
8703
- dispatchAction(action, campaignId) {
9203
+ dispatchAction(action, _campaignId) {
8704
9204
  var _a;
8705
9205
  let handled = false;
8706
9206
  try {
8707
9207
  handled = ((_a = this.onActionHandler) == null ? void 0 : _a.call(this, action)) === true;
8708
9208
  } catch {
8709
9209
  }
8710
- if (typeof window !== "undefined") {
8711
- const legacy = this._legacyEventFor(action, campaignId);
8712
- if (legacy) {
8713
- const evt = new CustomEvent(legacy.name, { detail: legacy.detail, cancelable: true });
8714
- const proceed = window.dispatchEvent(evt);
8715
- if (!proceed || evt.defaultPrevented) handled = true;
8716
- }
8717
- }
8718
9210
  if (!handled) {
8719
9211
  const url = fallbackUrlForAction(action, this.productUrl);
8720
9212
  if (url && typeof window !== "undefined") window.location.href = url;
8721
9213
  }
8722
9214
  }
8723
- /** Back-compat mapping: canonical action → the legacy DOM event hosts listen
8724
- * for today. `add_to_cart` always emits `aegis:add-to-cart` (the one ACS
8725
- * handles), collapsing the old `aegis:cart-add` divergence. */
8726
- _legacyEventFor(a, campaignId) {
8727
- const base = { campaign_id: campaignId };
8728
- switch (a.type) {
8729
- case "add_to_cart":
8730
- return {
8731
- name: "aegis:add-to-cart",
8732
- detail: {
8733
- ...base,
8734
- product_id: a.productId,
8735
- sku: a.sku,
8736
- title: a.title,
8737
- price: a.price,
8738
- image_url: a.imageUrl,
8739
- cta_url: a.url,
8740
- qty: a.qty ?? 1
8741
- }
8742
- };
8743
- case "open_product":
8744
- return { name: "aegis:open-product", detail: { ...base, sku: a.sku, product_id: a.productId, url: a.url } };
8745
- case "open_cart":
8746
- return { name: "aegis:open-cart", detail: base };
8747
- case "open_login":
8748
- return { name: "aegis:open-login", detail: base };
8749
- default:
8750
- return null;
8751
- }
8752
- }
8753
9215
  /**
8754
9216
  * Phase-2 CTA action verbs (`aegis://<verb>?<params>`). Performs an in-app
8755
9217
  * behaviour instead of navigating. Verbs:
@@ -8789,9 +9251,9 @@ const _AegisInAppManager = class _AegisInAppManager {
8789
9251
  /** Remove a rendered campaign's element(s) from the DOM by campaign id. */
8790
9252
  removeCampaignEl(campaignId) {
8791
9253
  if (typeof document === "undefined") return;
8792
- document.querySelectorAll(`[data-campaign-id="${campaignId}"]`).forEach((el) => {
9254
+ document.querySelectorAll(`[data-campaign-id="${campaignId}"]`).forEach((el2) => {
8793
9255
  var _a;
8794
- return (_a = el.parentNode) == null ? void 0 : _a.removeChild(el);
9256
+ return (_a = el2.parentNode) == null ? void 0 : _a.removeChild(el2);
8795
9257
  });
8796
9258
  }
8797
9259
  /**
@@ -10518,9 +10980,9 @@ const _AegisInAppManager = class _AegisInAppManager {
10518
10980
  if (typeof document !== "undefined") {
10519
10981
  document.querySelectorAll(
10520
10982
  ".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"
10521
- ).forEach((el) => {
10522
- if (el.parentNode) {
10523
- el.parentNode.removeChild(el);
10983
+ ).forEach((el2) => {
10984
+ if (el2.parentNode) {
10985
+ el2.parentNode.removeChild(el2);
10524
10986
  }
10525
10987
  });
10526
10988
  const styles = document.getElementById("aegis-in-app-styles");
@@ -10552,9 +11014,9 @@ let AegisInAppManager = _AegisInAppManager;
10552
11014
  function renderPreview(config, opts) {
10553
11015
  document.querySelectorAll(
10554
11016
  '[class^="aegis-in-app-"]'
10555
- ).forEach((el) => {
10556
- if (el.parentNode) {
10557
- el.parentNode.removeChild(el);
11017
+ ).forEach((el2) => {
11018
+ if (el2.parentNode) {
11019
+ el2.parentNode.removeChild(el2);
10558
11020
  }
10559
11021
  });
10560
11022
  try {
@@ -10590,7 +11052,7 @@ function renderPreview(config, opts) {
10590
11052
  if (anchors.length > 0) {
10591
11053
  try {
10592
11054
  anchors.forEach((slot) => {
10593
- slot.querySelectorAll(":scope > :not([data-aegis-slot-default])").forEach((el) => el.remove());
11055
+ slot.querySelectorAll(":scope > :not([data-aegis-slot-default])").forEach((el2) => el2.remove());
10594
11056
  });
10595
11057
  const mm = manager;
10596
11058
  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));
@@ -10611,17 +11073,17 @@ function renderPreview(config, opts) {
10611
11073
  m2.displayCampaign(config);
10612
11074
  }
10613
11075
  const MAX_NODES = 400;
10614
- function selectorFor(el) {
10615
- if (el.id) return `#${CSS.escape(el.id)}`;
11076
+ function selectorFor(el2) {
11077
+ if (el2.id) return `#${CSS.escape(el2.id)}`;
10616
11078
  for (const attr of ["data-testid", "data-test", "data-aegis-anchor", "data-qa", "name"]) {
10617
- const v = el.getAttribute(attr);
11079
+ const v = el2.getAttribute(attr);
10618
11080
  if (v) {
10619
- const sel = `${el.tagName.toLowerCase()}[${attr}="${CSS.escape(v)}"]`;
11081
+ const sel = `${el2.tagName.toLowerCase()}[${attr}="${CSS.escape(v)}"]`;
10620
11082
  if (document.querySelectorAll(sel).length === 1) return sel;
10621
11083
  }
10622
11084
  }
10623
11085
  const parts = [];
10624
- let node = el;
11086
+ let node = el2;
10625
11087
  while (node && node !== document.body && parts.length < 6) {
10626
11088
  if (node.id) {
10627
11089
  parts.unshift(`#${CSS.escape(node.id)}`);
@@ -10645,15 +11107,15 @@ function captureWebScreen(screenName) {
10645
11107
  const candidates = document.querySelectorAll(
10646
11108
  "a,button,input,select,textarea,[role],[data-aegis-anchor],[data-testid],h1,h2,h3,img,[data-aegis-slot]"
10647
11109
  );
10648
- for (const el of Array.from(candidates)) {
11110
+ for (const el2 of Array.from(candidates)) {
10649
11111
  if (nodes.length >= MAX_NODES) break;
10650
- const rect = el.getBoundingClientRect();
11112
+ const rect = el2.getBoundingClientRect();
10651
11113
  if (rect.width <= 0 || rect.height <= 0) continue;
10652
- const slotKey = el.getAttribute("data-aegis-slot");
10653
- const role = slotKey ? `slot:${slotKey}` : el.getAttribute("role") || el.tagName.toLowerCase();
10654
- const text = (el.textContent || el.getAttribute("aria-label") || "").trim().slice(0, 80);
11114
+ const slotKey = el2.getAttribute("data-aegis-slot");
11115
+ const role = slotKey ? `slot:${slotKey}` : el2.getAttribute("role") || el2.tagName.toLowerCase();
11116
+ const text = (el2.textContent || el2.getAttribute("aria-label") || "").trim().slice(0, 80);
10655
11117
  nodes.push({
10656
- selector: selectorFor(el),
11118
+ selector: selectorFor(el2),
10657
11119
  role,
10658
11120
  ...text ? { text } : {},
10659
11121
  bounds: {