@active-reach/web-sdk 1.35.0 → 1.35.4

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
@@ -1,5 +1,5 @@
1
- import { S as Storage, l as logger, A as Aegis } from "./analytics-CCDZCnut.mjs";
2
- import { B, E, N, R, U, m } from "./analytics-CCDZCnut.mjs";
1
+ import { S as Storage, l as logger, A as Aegis } from "./analytics-ZwrwqCTJ.mjs";
2
+ import { B, E, N, R, U, m } from "./analytics-ZwrwqCTJ.mjs";
3
3
  import { AegisWebPush } from "./push/AegisWebPush.js";
4
4
  function debounce(func, wait) {
5
5
  let timeoutId = null;
@@ -524,7 +524,8 @@ function applyPerCardOverride(tpl, look) {
524
524
  if (look.hide_price) drop("price");
525
525
  if (look.hide_cta) drop("cta");
526
526
  if (look.hide_rating) drop("rating");
527
- if (look.cta_text) elements = elements.map((e) => e.type === "cta" ? { ...e, text: look.cta_text } : e);
527
+ if (look.cta_text)
528
+ elements = elements.map((e) => e.type === "cta" ? { ...e, text: look.cta_text } : e);
528
529
  if (look.cta_action || look.cta_url || look.cta_custom_name) {
529
530
  elements = elements.map(
530
531
  (e) => e.type === "cta" ? {
@@ -537,7 +538,9 @@ function applyPerCardOverride(tpl, look) {
537
538
  }
538
539
  if (look.badge) {
539
540
  const badgeEl = { type: "badge", text: look.badge, color: look.badge_color };
540
- elements = elements.some((e) => e.type === "badge") ? elements.map((e) => e.type === "badge" ? badgeEl : e) : [elements[0], badgeEl, ...elements.slice(1)].filter(Boolean);
541
+ elements = elements.some((e) => e.type === "badge") ? elements.map((e) => e.type === "badge" ? badgeEl : e) : [elements[0], badgeEl, ...elements.slice(1)].filter(
542
+ Boolean
543
+ );
541
544
  }
542
545
  return { ...tpl, frame, elements };
543
546
  }
@@ -585,13 +588,12 @@ function paintRating(card) {
585
588
  const count = Number(meta.review_count ?? 0);
586
589
  const rr = document.createElement("div");
587
590
  rr.style.cssText = "display:flex;align-items:center;gap:3px;font-size:11px;line-height:1;";
588
- const star2 = document.createElement("span");
589
- star2.textContent = "★";
590
- star2.style.cssText = "color:#059669;";
591
+ const star2 = lucideSvgEl("star", { size: 12, color: "#059669" });
592
+ if (star2) star2.setAttribute("fill", "#059669");
591
593
  const val = document.createElement("span");
592
594
  val.textContent = avg.toFixed(1);
593
595
  val.style.cssText = "color:#059669;font-weight:700;";
594
- rr.appendChild(star2);
596
+ if (star2) rr.appendChild(star2);
595
597
  rr.appendChild(val);
596
598
  if (count > 0) {
597
599
  const c = document.createElement("span");
@@ -650,7 +652,9 @@ function appendRatingThanks(wrap, el2, ctx, fg, accent, isHigh) {
650
652
  cta.textContent = el2.rating_high_cta || "Leave a review";
651
653
  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;`;
652
654
  cta.addEventListener("click", () => {
653
- ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "rating_input", cta: "high_review" } });
655
+ ctx.trackEvent(ctx.campaign.id, "clicked", {
656
+ meta: { element: "rating_input", cta: "high_review" }
657
+ });
654
658
  if (ctx.navigate) ctx.navigate(url, "cta");
655
659
  else window.location.href = url;
656
660
  });
@@ -753,6 +757,315 @@ function buildRatingInputElement(el2, ctx, fg) {
753
757
  wrap.appendChild(row);
754
758
  return wrap;
755
759
  }
760
+ function starRow(scale, onPick) {
761
+ const row = document.createElement("div");
762
+ row.style.cssText = "display:flex;gap:2px;flex-shrink:0;";
763
+ const stars2 = [];
764
+ let picked = 0;
765
+ const paint = (upto) => {
766
+ stars2.forEach((s, idx) => {
767
+ const on = idx < upto;
768
+ s.setAttribute("stroke", on ? "#f59e0b" : "#d1d5db");
769
+ s.setAttribute("fill", on ? "#f59e0b" : "none");
770
+ });
771
+ };
772
+ for (let i = 1; i <= scale; i++) {
773
+ const btn = document.createElement("button");
774
+ btn.type = "button";
775
+ btn.setAttribute("aria-label", `${i} star${i > 1 ? "s" : ""}`);
776
+ btn.style.cssText = "cursor:pointer;border:none;background:none;padding:1px;line-height:0;";
777
+ const svg = lucideSvgEl("star", { size: 22, color: "#d1d5db" });
778
+ if (svg) {
779
+ btn.appendChild(svg);
780
+ stars2.push(svg);
781
+ }
782
+ btn.addEventListener("mouseenter", () => paint(i));
783
+ btn.addEventListener("mouseleave", () => paint(picked));
784
+ btn.addEventListener("click", () => {
785
+ picked = i;
786
+ paint(i);
787
+ onPick(i);
788
+ });
789
+ row.appendChild(btn);
790
+ }
791
+ return row;
792
+ }
793
+ function ratingThumb(it, accent) {
794
+ const box = document.createElement("div");
795
+ box.style.cssText = `width:40px;height:40px;flex-shrink:0;border-radius:8px;overflow:hidden;background:${accent}1f;display:flex;align-items:center;justify-content:center;`;
796
+ if (it.image_url) {
797
+ const img = document.createElement("img");
798
+ img.src = it.image_url;
799
+ img.alt = it.name || "";
800
+ img.loading = "lazy";
801
+ img.style.cssText = "width:100%;height:100%;object-fit:cover;";
802
+ box.appendChild(img);
803
+ } else {
804
+ const mono = document.createElement("span");
805
+ mono.textContent = (it.name || "?").slice(0, 1).toUpperCase();
806
+ mono.style.cssText = `font-size:16px;font-weight:700;color:${accent};`;
807
+ box.appendChild(mono);
808
+ }
809
+ return box;
810
+ }
811
+ function buildProductRatingListElement(el2, ctx, fg) {
812
+ const wrap = document.createElement("div");
813
+ wrap.style.cssText = "display:flex;flex-direction:column;gap:10px;margin:2px 0;";
814
+ const slot = ctx.slotEl;
815
+ if (!slot) return wrap;
816
+ const accent = ctx.sanitizeColor(
817
+ slot.getAttribute("data-accent") || ctx.campaign.background_color || "#4169e1"
818
+ );
819
+ const orderId = slot.getAttribute("data-order-id");
820
+ const billCode = slot.getAttribute("data-bill-code");
821
+ const token = slot.getAttribute("data-token");
822
+ const reviewsUrl = slot.getAttribute("data-reviews-url") || el2.rating_high_url || null;
823
+ const scale = typeof el2.rating_scale === "number" && el2.rating_scale >= 2 && el2.rating_scale <= 10 ? Math.floor(el2.rating_scale) : 5;
824
+ const showThumb = el2.show_thumbnail !== false && el2.rating_layout !== "compact";
825
+ const scores = {};
826
+ const allowText = el2.allow_review_text === true;
827
+ const canUpload = !!(ctx.apiHost && ctx.writeKey && ctx.organizationId);
828
+ const allowMedia = el2.allow_review_media === true && canUpload;
829
+ let reviewText = "";
830
+ const mediaUrls = [];
831
+ const fileToB64 = (file) => new Promise((resolve, reject) => {
832
+ const r = new FileReader();
833
+ r.onload = () => {
834
+ const s = String(r.result || "");
835
+ resolve(s.includes(",") ? s.split(",")[1] : s);
836
+ };
837
+ r.onerror = () => reject(r.error);
838
+ r.readAsDataURL(file);
839
+ });
840
+ const uploadOne = async (file) => {
841
+ if (!ctx.apiHost || !ctx.writeKey || !ctx.organizationId) return null;
842
+ try {
843
+ const data = await fileToB64(file);
844
+ const res = await fetch(`${ctx.apiHost}/v1/in-app/review-media`, {
845
+ method: "POST",
846
+ headers: {
847
+ "Content-Type": "application/json",
848
+ "X-Aegis-Write-Key": ctx.writeKey,
849
+ "X-Organization-ID": ctx.organizationId
850
+ },
851
+ body: JSON.stringify({ data, content_type: file.type })
852
+ });
853
+ if (!res.ok) return null;
854
+ const j = await res.json();
855
+ return j.url ? { url: j.url, type: j.type === "video" ? "video" : "image" } : null;
856
+ } catch {
857
+ return null;
858
+ }
859
+ };
860
+ const buildComposer = () => {
861
+ const box = document.createElement("div");
862
+ box.style.cssText = "display:flex;flex-direction:column;gap:8px;margin-top:2px;";
863
+ const toggle = document.createElement("button");
864
+ toggle.type = "button";
865
+ toggle.textContent = allowMedia ? "+ Add a written review & photos" : "+ Add a written review";
866
+ toggle.style.cssText = `align-self:flex-start;border:none;background:none;cursor:pointer;color:${accent};font-size:13px;font-weight:700;padding:2px 0;`;
867
+ const panel = document.createElement("div");
868
+ panel.style.cssText = "display:none;flex-direction:column;gap:8px;";
869
+ toggle.addEventListener("click", () => {
870
+ panel.style.display = "flex";
871
+ toggle.style.display = "none";
872
+ });
873
+ const hint = document.createElement("div");
874
+ hint.style.cssText = `font-size:11px;color:${fg};opacity:.65;`;
875
+ if (typeof el2.review_earn_hint === "string" && el2.review_earn_hint.trim()) {
876
+ hint.textContent = el2.review_earn_hint.trim();
877
+ panel.appendChild(hint);
878
+ } else if (ctx.apiHost && ctx.writeKey && ctx.organizationId) {
879
+ panel.appendChild(hint);
880
+ const url = `${ctx.apiHost}/v1/in-app/review-earn-hint`;
881
+ void fetch(url, {
882
+ headers: { "X-Aegis-Write-Key": ctx.writeKey, "X-Organization-ID": ctx.organizationId }
883
+ }).then((r) => r.ok ? r.json() : null).then((d) => {
884
+ if (!d || d.enabled !== true) return;
885
+ const name = typeof d.points_name === "string" ? d.points_name : "points";
886
+ const parts = [`Earn ${Number(d.base)} ${name}`];
887
+ if (Number(d.photo_bonus) > 0) parts.push(`+${Number(d.photo_bonus)} photo`);
888
+ if (Number(d.video_bonus) > 0) parts.push(`+${Number(d.video_bonus)} video`);
889
+ hint.textContent = parts.join(" · ");
890
+ }).catch(() => {
891
+ });
892
+ }
893
+ if (allowText) {
894
+ const ta = document.createElement("textarea");
895
+ ta.placeholder = "Tell us more (optional)";
896
+ ta.rows = 3;
897
+ ta.style.cssText = `width:100%;box-sizing:border-box;border:1px solid #e5e7eb;border-radius:10px;padding:8px 10px;font-size:13px;color:${fg};font-family:inherit;resize:vertical;`;
898
+ ta.addEventListener("input", () => {
899
+ reviewText = ta.value;
900
+ });
901
+ panel.appendChild(ta);
902
+ }
903
+ if (allowMedia) {
904
+ const strip = document.createElement("div");
905
+ strip.style.cssText = "display:flex;gap:8px;flex-wrap:wrap;align-items:center;";
906
+ const input = document.createElement("input");
907
+ input.type = "file";
908
+ input.accept = "image/*,video/*";
909
+ input.multiple = true;
910
+ input.style.cssText = "display:none;";
911
+ const addTile = document.createElement("button");
912
+ addTile.type = "button";
913
+ addTile.setAttribute("aria-label", "Add photo or video");
914
+ addTile.style.cssText = `width:48px;height:48px;flex-shrink:0;border:1px dashed ${accent};border-radius:10px;background:${accent}14;color:${accent};cursor:pointer;display:flex;align-items:center;justify-content:center;`;
915
+ const plus = lucideSvgEl("plus", { size: 22, color: accent });
916
+ if (plus) addTile.appendChild(plus);
917
+ addTile.addEventListener("click", () => input.click());
918
+ input.addEventListener("change", () => {
919
+ const files = Array.from(input.files || []);
920
+ input.value = "";
921
+ files.forEach((f) => {
922
+ if (mediaUrls.length >= 6) return;
923
+ const thumb = document.createElement("div");
924
+ thumb.style.cssText = "width:48px;height:48px;flex-shrink:0;border-radius:10px;background:#f3f4f6;display:flex;align-items:center;justify-content:center;overflow:hidden;font-size:11px;color:#9ca3af;";
925
+ thumb.textContent = "…";
926
+ strip.insertBefore(thumb, addTile);
927
+ void uploadOne(f).then((up) => {
928
+ if (!up) {
929
+ thumb.textContent = "!";
930
+ return;
931
+ }
932
+ mediaUrls.push(up);
933
+ thumb.textContent = "";
934
+ if (up.type === "video") {
935
+ const vid = lucideSvgEl("play", { size: 20, color: "#6b7280" });
936
+ if (vid) thumb.appendChild(vid);
937
+ } else {
938
+ const img = document.createElement("img");
939
+ img.src = up.url;
940
+ img.style.cssText = "width:100%;height:100%;object-fit:cover;";
941
+ thumb.appendChild(img);
942
+ }
943
+ });
944
+ });
945
+ });
946
+ strip.appendChild(addTile);
947
+ strip.appendChild(input);
948
+ panel.appendChild(strip);
949
+ }
950
+ box.appendChild(toggle);
951
+ box.appendChild(panel);
952
+ return box;
953
+ };
954
+ const submitBtn = document.createElement("button");
955
+ submitBtn.type = "button";
956
+ submitBtn.textContent = "Submit ratings";
957
+ submitBtn.style.cssText = `margin-top:2px;width:100%;border:0;border-radius:999px;background:${accent};color:#fff;font-size:14px;font-weight:700;padding:10px 16px;cursor:pointer;opacity:.5;pointer-events:none;`;
958
+ const refreshSubmit = () => {
959
+ const has = Object.keys(scores).length > 0;
960
+ submitBtn.style.opacity = has ? "1" : ".5";
961
+ submitBtn.style.pointerEvents = has ? "auto" : "none";
962
+ };
963
+ const done = (branch) => {
964
+ var _a;
965
+ wrap.innerHTML = "";
966
+ const ack = document.createElement("div");
967
+ ack.style.cssText = "text-align:center;padding:8px 0;";
968
+ const t = document.createElement("p");
969
+ t.textContent = typeof el2.thanks_text === "string" && el2.thanks_text.trim() ? el2.thanks_text : "Thanks for rating your items!";
970
+ t.style.cssText = `margin:0;font-size:14px;font-weight:700;color:${fg};`;
971
+ ack.appendChild(t);
972
+ if (branch === "positive" && reviewsUrl) {
973
+ const a = document.createElement("a");
974
+ a.href = ctx.sanitizeUrl(reviewsUrl) || "#";
975
+ a.textContent = el2.rating_high_cta || "Share a review";
976
+ a.style.cssText = `display:inline-block;margin-top:6px;font-size:12px;font-weight:700;color:${accent};text-decoration:none;`;
977
+ ack.appendChild(a);
978
+ }
979
+ wrap.appendChild(ack);
980
+ (_a = ctx.playReaction) == null ? void 0 : _a.call(ctx, wrap, branch === "positive" ? "affirm" : "acknowledge");
981
+ };
982
+ let submitting = false;
983
+ submitBtn.addEventListener("click", () => {
984
+ var _a;
985
+ const rated = Object.keys(scores);
986
+ if (submitting || rated.length === 0) return;
987
+ submitting = true;
988
+ submitBtn.disabled = true;
989
+ submitBtn.style.opacity = ".6";
990
+ submitBtn.textContent = "Saving…";
991
+ ctx.trackEvent(ctx.campaign.id, "clicked", {
992
+ meta: { element: "product_rating_list", items: rated.length }
993
+ });
994
+ Promise.resolve(
995
+ (_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "rating", {
996
+ items: rated.map((pid) => ({ product_id: pid, score: scores[pid] })),
997
+ order_id: orderId,
998
+ bill_code: billCode,
999
+ token,
1000
+ ...reviewText.trim() ? { review_text: reviewText.trim() } : {},
1001
+ ...mediaUrls.length ? { media_urls: mediaUrls } : {}
1002
+ })
1003
+ ).then((res) => {
1004
+ var _a2;
1005
+ const branch = ((_a2 = res == null ? void 0 : res.rating_result) == null ? void 0 : _a2.branch) || "positive";
1006
+ done(branch);
1007
+ }).catch(() => {
1008
+ submitting = false;
1009
+ submitBtn.disabled = false;
1010
+ submitBtn.style.opacity = "1";
1011
+ submitBtn.textContent = "Submit ratings";
1012
+ });
1013
+ });
1014
+ const render = (items) => {
1015
+ wrap.innerHTML = "";
1016
+ const clean = (items || []).filter((it) => it && it.product_id);
1017
+ if (clean.length === 0) return;
1018
+ const list = document.createElement("div");
1019
+ list.style.cssText = "display:flex;flex-direction:column;gap:10px;";
1020
+ clean.forEach((it) => {
1021
+ const rowEl = document.createElement("div");
1022
+ rowEl.style.cssText = "display:flex;align-items:center;gap:10px;";
1023
+ if (showThumb) rowEl.appendChild(ratingThumb(it, accent));
1024
+ const nameEl = document.createElement("span");
1025
+ nameEl.textContent = it.name || it.product_id;
1026
+ nameEl.style.cssText = `flex:1;min-width:0;font-size:13px;color:${fg};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`;
1027
+ rowEl.appendChild(nameEl);
1028
+ rowEl.appendChild(
1029
+ starRow(scale, (n) => {
1030
+ scores[it.product_id] = n;
1031
+ refreshSubmit();
1032
+ })
1033
+ );
1034
+ list.appendChild(rowEl);
1035
+ });
1036
+ wrap.appendChild(list);
1037
+ if (allowText || allowMedia) wrap.appendChild(buildComposer());
1038
+ wrap.appendChild(submitBtn);
1039
+ };
1040
+ const rawAttr = slot.getAttribute("data-rateable-items");
1041
+ if (rawAttr) {
1042
+ try {
1043
+ const parsed = JSON.parse(rawAttr);
1044
+ render(Array.isArray(parsed) ? parsed : []);
1045
+ } catch {
1046
+ }
1047
+ } else if (token && ctx.apiHost && ctx.writeKey) {
1048
+ const loader = document.createElement("div");
1049
+ loader.textContent = "Loading your items…";
1050
+ loader.style.cssText = `font-size:13px;color:${fg};opacity:.6;padding:6px 0;`;
1051
+ wrap.appendChild(loader);
1052
+ const url = `${ctx.apiHost}/v1/in-app/rateable-items?token=${encodeURIComponent(token)}`;
1053
+ const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
1054
+ if (ac)
1055
+ setTimeout(() => {
1056
+ try {
1057
+ ac.abort();
1058
+ } catch {
1059
+ }
1060
+ }, 6e3);
1061
+ fetch(url, { headers: { "X-Aegis-Write-Key": ctx.writeKey }, signal: ac == null ? void 0 : ac.signal }).then((r) => r.ok ? r.json() : null).then((data) => {
1062
+ render(Array.isArray(data == null ? void 0 : data.items) ? data.items : []);
1063
+ }).catch(() => {
1064
+ wrap.innerHTML = "";
1065
+ });
1066
+ }
1067
+ return wrap;
1068
+ }
756
1069
  function appendThanks(wrap, el2, fg, fallback) {
757
1070
  const msg = typeof el2.thanks_text === "string" && el2.thanks_text.trim() ? el2.thanks_text : fallback;
758
1071
  if (!msg) return;
@@ -796,7 +1109,9 @@ function buildPollElement(el2, ctx, fg, accent) {
796
1109
  btn.style.background = accent;
797
1110
  btn.style.color = "#fff";
798
1111
  btn.style.opacity = "1";
799
- ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "poll", option_index: idx, option_label: label } });
1112
+ ctx.trackEvent(ctx.campaign.id, "clicked", {
1113
+ meta: { element: "poll", option_index: idx, option_label: label }
1114
+ });
800
1115
  void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "poll", { option_index: idx, option_label: label }));
801
1116
  (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, "affirm");
802
1117
  appendThanks(wrap, el2, fg, "Thanks for your answer!");
@@ -819,7 +1134,9 @@ function appendNpsThanks(wrap, el2, ctx, fg, accent, isPromoter) {
819
1134
  cta.textContent = el2.nps_promoter_cta || "Leave a review";
820
1135
  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;`;
821
1136
  cta.addEventListener("click", () => {
822
- ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "nps", cta: "promoter_review" } });
1137
+ ctx.trackEvent(ctx.campaign.id, "clicked", {
1138
+ meta: { element: "nps", cta: "promoter_review" }
1139
+ });
823
1140
  if (ctx.navigate) ctx.navigate(url, "cta");
824
1141
  else window.location.href = url;
825
1142
  });
@@ -864,7 +1181,11 @@ function renderNpsReasonStep(wrap, el2, ctx, score, fg, accent) {
864
1181
  done = true;
865
1182
  const comment = withComment ? ta.value.trim() : "";
866
1183
  void ((_a = ctx.submitResponse) == null ? void 0 : _a.call(ctx, "nps", comment ? { score, band, comment } : { score, band }));
867
- (_b = ctx.playReaction) == null ? void 0 : _b.call(ctx, wrap, score >= promoterThreshold ? "affirm" : score <= 6 ? "empathize" : "acknowledge");
1184
+ (_b = ctx.playReaction) == null ? void 0 : _b.call(
1185
+ ctx,
1186
+ wrap,
1187
+ score >= promoterThreshold ? "affirm" : score <= 6 ? "empathize" : "acknowledge"
1188
+ );
868
1189
  appendNpsThanks(wrap, el2, ctx, fg, accent, score >= promoterThreshold);
869
1190
  };
870
1191
  const btnRow = document.createElement("div");
@@ -920,7 +1241,9 @@ function buildNpsElement(el2, ctx, fg, accent, muted) {
920
1241
  btn.style.color = "#fff";
921
1242
  btn.style.opacity = "1";
922
1243
  const npsPayload = { score: n, band: npsBandFromScore(n) };
923
- ctx.trackEvent(ctx.campaign.id, "clicked", { meta: { element: "nps", score: n, band: npsPayload.band } });
1244
+ ctx.trackEvent(ctx.campaign.id, "clicked", {
1245
+ meta: { element: "nps", score: n, band: npsPayload.band }
1246
+ });
924
1247
  if (el2.nps_followup_enabled === true) {
925
1248
  renderNpsReasonStep(wrap, el2, ctx, n, fg, accent);
926
1249
  } else {
@@ -1037,13 +1360,21 @@ function buildCtaAction(el2, card, meta, inline, sanitizeUrl) {
1037
1360
  const sku = meta.sku;
1038
1361
  const platformProductId = meta.platform_product_id;
1039
1362
  const platformUrl = safe(meta.platform_url);
1363
+ const platformVariantId = meta.platform_variant_id;
1040
1364
  switch (kind) {
1041
1365
  case "open_cart":
1042
1366
  return { type: "open_cart" };
1043
1367
  case "open_login":
1044
1368
  return { type: "open_login" };
1045
1369
  case "open_product":
1046
- return { type: "open_product", productId, sku, url: platformUrl || url, platformProductId, platformUrl };
1370
+ return {
1371
+ type: "open_product",
1372
+ productId,
1373
+ sku,
1374
+ url: platformUrl || url,
1375
+ platformProductId,
1376
+ platformUrl
1377
+ };
1047
1378
  case "navigate":
1048
1379
  return { type: "navigate", url: url || "" };
1049
1380
  case "custom":
@@ -1055,6 +1386,7 @@ function buildCtaAction(el2, card, meta, inline, sanitizeUrl) {
1055
1386
  productId,
1056
1387
  sku,
1057
1388
  platformProductId,
1389
+ platformVariantId,
1058
1390
  title: card.title,
1059
1391
  price: meta.price ?? card.body,
1060
1392
  imageUrl: card.image_url,
@@ -1233,6 +1565,10 @@ function renderCardFromTemplate(card, opts) {
1233
1565
  container.appendChild(buildNpsElement(el2, ctx, fg, brand, muted));
1234
1566
  break;
1235
1567
  }
1568
+ case "product_rating_list": {
1569
+ container.appendChild(buildProductRatingListElement(el2, ctx, fg));
1570
+ break;
1571
+ }
1236
1572
  case "group": {
1237
1573
  const g = document.createElement("div");
1238
1574
  const gdir = el2.layout === "row" ? "row" : "column";
@@ -1256,7 +1592,10 @@ function renderCardFromTemplate(card, opts) {
1256
1592
  var _a;
1257
1593
  e.stopPropagation();
1258
1594
  trackEvent(campaign.id, "clicked", { stepId: el2.id || "add" });
1259
- (_a = ctx.dispatchAction) == null ? void 0 : _a.call(ctx, buildCtaAction(el2, card, meta, inline, sanitizeUrl));
1595
+ (_a = ctx.dispatchAction) == null ? void 0 : _a.call(
1596
+ ctx,
1597
+ buildCtaAction(el2, card, meta, inline, sanitizeUrl)
1598
+ );
1260
1599
  });
1261
1600
  (overlayOnImage ? imageWrap : container).appendChild(cta);
1262
1601
  break;
@@ -3724,6 +4063,7 @@ class AegisChat {
3724
4063
  this.pendingEcho = [];
3725
4064
  this.hasInteracted = false;
3726
4065
  this.writeKey = config.writeKey;
4066
+ this.organizationId = config.organizationId;
3727
4067
  this.apiHost = (config.apiHost ?? "").replace(/\/$/, "");
3728
4068
  this.contactId = config.contactId;
3729
4069
  this.channel = config.channel ?? "web";
@@ -3913,6 +4253,7 @@ class AegisChat {
3913
4253
  const url = new URL("/v1/stream/realtime", this.apiHost || window.location.origin);
3914
4254
  url.searchParams.set("ticket", ticket);
3915
4255
  url.searchParams.set("mode", "ephemeral");
4256
+ if (this.organizationId) url.searchParams.set("org", this.organizationId);
3916
4257
  try {
3917
4258
  this.eventSource = new EventSource(url.toString());
3918
4259
  this.eventSource.onopen = () => {
@@ -5027,7 +5368,7 @@ function disposeActiveWebChat() {
5027
5368
  activeInstance = null;
5028
5369
  }
5029
5370
  const SVG_NS = "http://www.w3.org/2000/svg";
5030
- function el$1(tag, css, text) {
5371
+ function el(tag, css, text) {
5031
5372
  const e = document.createElement(tag);
5032
5373
  if (css) e.style.cssText = css;
5033
5374
  if (text != null) e.textContent = text;
@@ -5048,13 +5389,13 @@ function star(filled, size) {
5048
5389
  return svg;
5049
5390
  }
5050
5391
  function stars(rating, size = 15) {
5051
- const wrap = el$1("span", "display:inline-flex;gap:2px;vertical-align:middle;");
5392
+ const wrap = el("span", "display:inline-flex;gap:2px;vertical-align:middle;");
5052
5393
  wrap.setAttribute("aria-label", `${rating} out of 5`);
5053
5394
  for (let i = 1; i <= 5; i++) wrap.appendChild(star(i <= rating, size));
5054
5395
  return wrap;
5055
5396
  }
5056
5397
  function verifiedBadge() {
5057
- const b = el$1(
5398
+ const b = el(
5058
5399
  "span",
5059
5400
  "display:inline-flex;align-items:center;gap:3px;font-size:12px;font-weight:600;color:#059669;"
5060
5401
  );
@@ -5071,12 +5412,33 @@ function verifiedBadge() {
5071
5412
  p.setAttribute("stroke-linejoin", "round");
5072
5413
  svg.appendChild(p);
5073
5414
  b.appendChild(svg);
5074
- b.appendChild(el$1("span", "", "Verified purchase"));
5415
+ b.appendChild(el("span", "", "Verified purchase"));
5416
+ return b;
5417
+ }
5418
+ function incentivizedBadge() {
5419
+ const b = el(
5420
+ "span",
5421
+ "display:inline-flex;align-items:center;gap:3px;font-size:12px;font-weight:600;color:#6b7280;"
5422
+ );
5423
+ const svg = document.createElementNS(SVG_NS, "svg");
5424
+ svg.setAttribute("viewBox", "0 0 24 24");
5425
+ svg.setAttribute("width", "13");
5426
+ svg.setAttribute("height", "13");
5427
+ const p = document.createElementNS(SVG_NS, "path");
5428
+ p.setAttribute("d", "M20 12v9H4v-9M2 7h20v5H2zM12 7v14M12 7a2.5 2.5 0 0 0-2.5-2.5C8 4.5 7 6 7 7M12 7a2.5 2.5 0 0 1 2.5-2.5C16 4.5 17 6 17 7");
5429
+ p.setAttribute("fill", "none");
5430
+ p.setAttribute("stroke", "#6b7280");
5431
+ p.setAttribute("stroke-width", "2");
5432
+ p.setAttribute("stroke-linecap", "round");
5433
+ p.setAttribute("stroke-linejoin", "round");
5434
+ svg.appendChild(p);
5435
+ b.appendChild(svg);
5436
+ b.appendChild(el("span", "", "Incentivized"));
5075
5437
  return b;
5076
5438
  }
5077
5439
  function avatar(name, accent) {
5078
5440
  const initial = (name.trim()[0] || "?").toUpperCase();
5079
- const a = el$1(
5441
+ const a = el(
5080
5442
  "span",
5081
5443
  `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
5444
  initial
@@ -5084,7 +5446,7 @@ function avatar(name, accent) {
5084
5446
  return a;
5085
5447
  }
5086
5448
  function thumbBtn(up, count, reviewId, creds) {
5087
- const btn = el$1(
5449
+ const btn = el(
5088
5450
  "button",
5089
5451
  "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
5452
  );
@@ -5101,7 +5463,7 @@ function thumbBtn(up, count, reviewId, creds) {
5101
5463
  p.setAttribute("fill", "currentColor");
5102
5464
  svg.appendChild(p);
5103
5465
  btn.appendChild(svg);
5104
- const c = el$1("span", "", String(count));
5466
+ const c = el("span", "", String(count));
5105
5467
  btn.appendChild(c);
5106
5468
  const key = `aegis_rv_voted_${reviewId}`;
5107
5469
  let voted = false;
@@ -5179,7 +5541,7 @@ function renderReviewsInline(ctx, target, creds) {
5179
5541
  const productId = cfg.getAttribute("data-product-id") || ctx.campaign.product_id || null;
5180
5542
  const state = { sort: "recent", withPhotos: false };
5181
5543
  target.innerHTML = "";
5182
- target.appendChild(el$1("div", `font-family:${IN_APP_FONT_STACK};font-size:13px;color:#9ca3af;padding:10px 0;`, "Loading reviews…"));
5544
+ target.appendChild(el("div", `font-family:${IN_APP_FONT_STACK};font-size:13px;color:#9ca3af;padding:10px 0;`, "Loading reviews…"));
5183
5545
  function buildUrl() {
5184
5546
  const url = new URL("/v1/reviews/public", creds.apiHost);
5185
5547
  if (productId) url.searchParams.set("product_id", productId);
@@ -5194,20 +5556,21 @@ function renderReviewsInline(ctx, target, creds) {
5194
5556
  let listContainer = null;
5195
5557
  function reviewCard(r) {
5196
5558
  var _a, _b, _c;
5197
- const li = el$1("li", "padding:22px 0;border-top:1px solid #f0f1f3;");
5198
- const head = el$1("div", "display:flex;align-items:center;gap:10px;");
5559
+ const li = el("li", "padding:22px 0;border-top:1px solid #f0f1f3;");
5560
+ const head = el("div", "display:flex;align-items:center;gap:10px;");
5199
5561
  head.appendChild(avatar(((_a = r.reviewer_snapshot) == null ? void 0 : _a.name) ?? "Customer", accent));
5200
- const idn = el$1("div", "display:flex;flex-direction:column;gap:2px;min-width:0;");
5201
- const nameRow = el$1("div", "display:flex;flex-wrap:wrap;align-items:center;gap:8px;");
5202
- nameRow.appendChild(el$1("span", "font-size:15px;font-weight:700;color:#111827;", ((_b = r.reviewer_snapshot) == null ? void 0 : _b.name) ?? "Customer"));
5562
+ const idn = el("div", "display:flex;flex-direction:column;gap:2px;min-width:0;");
5563
+ const nameRow = el("div", "display:flex;flex-wrap:wrap;align-items:center;gap:8px;");
5564
+ nameRow.appendChild(el("span", "font-size:15px;font-weight:700;color:#111827;", ((_b = r.reviewer_snapshot) == null ? void 0 : _b.name) ?? "Customer"));
5203
5565
  if (r.verified_purchase) nameRow.appendChild(verifiedBadge());
5566
+ if (r.is_incentivized) nameRow.appendChild(incentivizedBadge());
5204
5567
  idn.appendChild(nameRow);
5205
5568
  idn.appendChild(stars(r.rating ?? 0, 14));
5206
5569
  head.appendChild(idn);
5207
5570
  if (r.created_at) {
5208
5571
  const d = new Date(r.created_at);
5209
5572
  if (!Number.isNaN(d.getTime())) {
5210
- head.appendChild(el$1(
5573
+ head.appendChild(el(
5211
5574
  "span",
5212
5575
  "margin-left:auto;font-size:12px;color:#9ca3af;white-space:nowrap;",
5213
5576
  d.toLocaleDateString(void 0, { day: "numeric", month: "short", year: "numeric" })
@@ -5216,10 +5579,10 @@ function renderReviewsInline(ctx, target, creds) {
5216
5579
  }
5217
5580
  li.appendChild(head);
5218
5581
  if (r.review_text) {
5219
- li.appendChild(el$1("p", "margin:12px 0 0;font-size:15px;line-height:1.6;color:#374151;", r.review_text));
5582
+ li.appendChild(el("p", "margin:12px 0 0;font-size:15px;line-height:1.6;color:#374151;", r.review_text));
5220
5583
  }
5221
5584
  if (r.media && r.media.length > 0) {
5222
- const gallery = el$1("div", "display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;");
5585
+ const gallery = el("div", "display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;");
5223
5586
  for (const m2 of r.media.slice(0, 6)) {
5224
5587
  const safe = ctx.sanitizeUrl(m2.url);
5225
5588
  if (!safe) continue;
@@ -5232,22 +5595,22 @@ function renderReviewsInline(ctx, target, creds) {
5232
5595
  }
5233
5596
  if (gallery.childElementCount) li.appendChild(gallery);
5234
5597
  }
5235
- const foot = el$1("div", "display:flex;align-items:center;gap:6px;margin-top:12px;");
5236
- foot.appendChild(el$1("span", "font-size:13px;color:#9ca3af;margin-right:2px;", "Was this helpful?"));
5598
+ const foot = el("div", "display:flex;align-items:center;gap:6px;margin-top:12px;");
5599
+ foot.appendChild(el("span", "font-size:13px;color:#9ca3af;margin-right:2px;", "Was this helpful?"));
5237
5600
  foot.appendChild(thumbBtn(true, r.helpful_count ?? 0, r.id, creds));
5238
5601
  foot.appendChild(thumbBtn(false, r.not_helpful_count ?? 0, r.id, creds));
5239
5602
  li.appendChild(foot);
5240
5603
  if ((_c = r.operator_reply) == null ? void 0 : _c.text) {
5241
- const reply = el$1(
5604
+ const reply = el(
5242
5605
  "div",
5243
5606
  `margin-top:14px;margin-left:44px;border-left:3px solid ${accent};background:${accent}0d;border-radius:0 12px 12px 0;padding:12px 14px;`
5244
5607
  );
5245
- const rlabel = el$1("div", "display:flex;align-items:center;gap:6px;margin-bottom:5px;");
5246
- rlabel.appendChild(el$1("span", `width:7px;height:7px;border-radius:999px;background:${accent};display:inline-block;`));
5247
- rlabel.appendChild(el$1("span", `font-size:12px;font-weight:700;color:${accent};`, brand));
5248
- rlabel.appendChild(el$1("span", "font-size:12px;color:#9ca3af;", "· replied"));
5608
+ const rlabel = el("div", "display:flex;align-items:center;gap:6px;margin-bottom:5px;");
5609
+ rlabel.appendChild(el("span", `width:7px;height:7px;border-radius:999px;background:${accent};display:inline-block;`));
5610
+ rlabel.appendChild(el("span", `font-size:12px;font-weight:700;color:${accent};`, brand));
5611
+ rlabel.appendChild(el("span", "font-size:12px;color:#9ca3af;", "· replied"));
5249
5612
  reply.appendChild(rlabel);
5250
- reply.appendChild(el$1("p", "margin:0;font-size:14px;line-height:1.55;color:#4b5563;", r.operator_reply.text));
5613
+ reply.appendChild(el("p", "margin:0;font-size:14px;line-height:1.55;color:#4b5563;", r.operator_reply.text));
5251
5614
  li.appendChild(reply);
5252
5615
  }
5253
5616
  return li;
@@ -5256,15 +5619,15 @@ function renderReviewsInline(ctx, target, creds) {
5256
5619
  if (!listContainer) return;
5257
5620
  listContainer.innerHTML = "";
5258
5621
  if (items.length === 0) {
5259
- listContainer.appendChild(el$1("p", "padding:20px 0;font-size:14px;color:#9ca3af;text-align:center;", "No reviews match this filter."));
5622
+ listContainer.appendChild(el("p", "padding:20px 0;font-size:14px;color:#9ca3af;text-align:center;", "No reviews match this filter."));
5260
5623
  return;
5261
5624
  }
5262
- const ul = el$1("ul", "list-style:none;margin:0;padding:0;");
5625
+ const ul = el("ul", "list-style:none;margin:0;padding:0;");
5263
5626
  for (const r of items) ul.appendChild(reviewCard(r));
5264
5627
  listContainer.appendChild(ul);
5265
5628
  }
5266
5629
  function pill(label, active, onClick) {
5267
- const b = el$1(
5630
+ const b = el(
5268
5631
  "button",
5269
5632
  `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;"),
5270
5633
  label
@@ -5273,7 +5636,7 @@ function renderReviewsInline(ctx, target, creds) {
5273
5636
  return b;
5274
5637
  }
5275
5638
  function controls(rerender) {
5276
- const bar = el$1("div", "display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:18px 0 4px;");
5639
+ const bar = el("div", "display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:18px 0 4px;");
5277
5640
  const sel = document.createElement("select");
5278
5641
  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;`;
5279
5642
  for (const s of SORTS) {
@@ -5300,29 +5663,29 @@ function renderReviewsInline(ctx, target, creds) {
5300
5663
  return bar;
5301
5664
  }
5302
5665
  function distributionPanel(dist, total) {
5303
- const box = el$1("div", "flex:0 1 300px;width:300px;max-width:100%;display:flex;flex-direction:column;gap:7px;");
5666
+ const box = el("div", "flex:0 1 300px;width:300px;max-width:100%;display:flex;flex-direction:column;gap:7px;");
5304
5667
  for (let r = 5; r >= 1; r--) {
5305
5668
  const n = dist[String(r)] ?? 0;
5306
5669
  const pct = total > 0 ? Math.round(n / total * 100) : 0;
5307
- const row = el$1("div", "display:flex;align-items:center;gap:10px;font-size:13px;color:#6b7280;");
5308
- const lbl = el$1("span", "width:30px;display:inline-flex;align-items:center;gap:3px;color:#374151;font-weight:600;", String(r));
5670
+ const row = el("div", "display:flex;align-items:center;gap:10px;font-size:13px;color:#6b7280;");
5671
+ const lbl = el("span", "width:30px;display:inline-flex;align-items:center;gap:3px;color:#374151;font-weight:600;", String(r));
5309
5672
  lbl.appendChild(star(true, 11));
5310
5673
  row.appendChild(lbl);
5311
- const track = el$1("div", "flex:1;height:8px;background:#f0f1f3;border-radius:999px;overflow:hidden;");
5312
- track.appendChild(el$1("div", `width:${pct}%;height:100%;background:${accent};border-radius:999px;`));
5674
+ const track = el("div", "flex:1;height:8px;background:#f0f1f3;border-radius:999px;overflow:hidden;");
5675
+ track.appendChild(el("div", `width:${pct}%;height:100%;background:${accent};border-radius:999px;`));
5313
5676
  row.appendChild(track);
5314
- row.appendChild(el$1("span", "width:24px;text-align:right;font-variant-numeric:tabular-nums;", String(n)));
5677
+ row.appendChild(el("span", "width:24px;text-align:right;font-variant-numeric:tabular-nums;", String(n)));
5315
5678
  box.appendChild(row);
5316
5679
  }
5317
5680
  return box;
5318
5681
  }
5319
5682
  function aiSummaryCard(summary) {
5320
- const card = el$1(
5683
+ const card = el(
5321
5684
  "div",
5322
5685
  "position:relative;border-radius:18px;padding:18px 20px;margin-bottom:24px;background:linear-gradient(135deg,#faf5ff 0%,#f5f3ff 100%);border:1px solid #ede9fe;"
5323
5686
  );
5324
- const head = el$1("div", "display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;");
5325
- const left = el$1("div", "display:flex;align-items:center;gap:7px;");
5687
+ const head = el("div", "display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;");
5688
+ const left = el("div", "display:flex;align-items:center;gap:7px;");
5326
5689
  const sp = document.createElementNS(SVG_NS, "svg");
5327
5690
  sp.setAttribute("viewBox", "0 0 24 24");
5328
5691
  sp.setAttribute("width", "16");
@@ -5332,17 +5695,17 @@ function renderReviewsInline(ctx, target, creds) {
5332
5695
  spp.setAttribute("fill", "#7c3aed");
5333
5696
  sp.appendChild(spp);
5334
5697
  left.appendChild(sp);
5335
- left.appendChild(el$1("span", "font-size:12px;font-weight:800;letter-spacing:.07em;color:#6d28d9;text-transform:uppercase;", "What customers say"));
5698
+ left.appendChild(el("span", "font-size:12px;font-weight:800;letter-spacing:.07em;color:#6d28d9;text-transform:uppercase;", "What customers say"));
5336
5699
  head.appendChild(left);
5337
- head.appendChild(el$1("span", "font-size:11px;font-weight:600;color:#a78bda;", "AI-generated"));
5700
+ head.appendChild(el("span", "font-size:11px;font-weight:600;color:#a78bda;", "AI-generated"));
5338
5701
  card.appendChild(head);
5339
- card.appendChild(el$1("p", "margin:0;font-size:15px;line-height:1.6;color:#3b2a63;", summary.text));
5340
- card.appendChild(el$1("p", "margin:8px 0 0;font-size:12px;color:#a78bda;text-align:right;", `from ${summary.review_count} reviews`));
5702
+ card.appendChild(el("p", "margin:0;font-size:15px;line-height:1.6;color:#3b2a63;", summary.text));
5703
+ card.appendChild(el("p", "margin:8px 0 0;font-size:12px;color:#a78bda;text-align:right;", `from ${summary.review_count} reviews`));
5341
5704
  return card;
5342
5705
  }
5343
5706
  function renderWall(data) {
5344
5707
  target.innerHTML = "";
5345
- const root = el$1(
5708
+ const root = el(
5346
5709
  "section",
5347
5710
  `font-family:${IN_APP_FONT_STACK};color:#111827;max-width:860px;margin:0 auto;-webkit-font-smoothing:antialiased;`
5348
5711
  );
@@ -5350,25 +5713,25 @@ function renderReviewsInline(ctx, target, creds) {
5350
5713
  const summary = data.aggregate.ai_summary;
5351
5714
  if (summary && summary.text) root.appendChild(aiSummaryCard(summary));
5352
5715
  if (data.aggregate.count === 0) {
5353
- root.appendChild(el$1("p", "font-size:15px;color:#9ca3af;padding:6px 0;", "No reviews yet — be the first to share your experience."));
5716
+ root.appendChild(el("p", "font-size:15px;color:#9ca3af;padding:6px 0;", "No reviews yet — be the first to share your experience."));
5354
5717
  target.appendChild(root);
5355
5718
  return;
5356
5719
  }
5357
- const header = el$1("div", "display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:24px 40px;padding-bottom:4px;");
5358
- const ratingBlock = el$1("div", "display:flex;flex-direction:column;gap:4px;");
5359
- const topRow = el$1("div", "display:flex;align-items:center;gap:12px;");
5720
+ const header = el("div", "display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:24px 40px;padding-bottom:4px;");
5721
+ const ratingBlock = el("div", "display:flex;flex-direction:column;gap:4px;");
5722
+ const topRow = el("div", "display:flex;align-items:center;gap:12px;");
5360
5723
  const avg = data.aggregate.avg_rating ?? 0;
5361
5724
  topRow.appendChild(stars(Math.round(avg), 20));
5362
- topRow.appendChild(el$1("span", "font-size:32px;font-weight:800;line-height:1;letter-spacing:-0.02em;", avg ? avg.toFixed(1) : "—"));
5725
+ topRow.appendChild(el("span", "font-size:32px;font-weight:800;line-height:1;letter-spacing:-0.02em;", avg ? avg.toFixed(1) : "—"));
5363
5726
  ratingBlock.appendChild(topRow);
5364
- ratingBlock.appendChild(el$1("span", "font-size:13px;color:#6b7280;", `${data.aggregate.count} review${data.aggregate.count === 1 ? "" : "s"} · 100% verified`));
5727
+ ratingBlock.appendChild(el("span", "font-size:13px;color:#6b7280;", `${data.aggregate.count} review${data.aggregate.count === 1 ? "" : "s"} · 100% verified`));
5365
5728
  header.appendChild(ratingBlock);
5366
5729
  header.appendChild(distributionPanel(data.aggregate.distribution, data.aggregate.count));
5367
5730
  root.appendChild(header);
5368
5731
  root.appendChild(controls(() => {
5369
5732
  void fetchReviews().then((d) => renderList((d == null ? void 0 : d.items) ?? []));
5370
5733
  }));
5371
- listContainer = el$1("div", "margin-top:2px;");
5734
+ listContainer = el("div", "margin-top:2px;");
5372
5735
  root.appendChild(listContainer);
5373
5736
  renderList(data.items);
5374
5737
  target.appendChild(root);
@@ -5380,23 +5743,23 @@ function renderReviewsInline(ctx, target, creds) {
5380
5743
  var _a;
5381
5744
  target.innerHTML = "";
5382
5745
  if (data.aggregate.count === 0) return;
5383
- const root = el$1(
5746
+ const root = el(
5384
5747
  "section",
5385
5748
  `font-family:${IN_APP_FONT_STACK};color:#111827;max-width:860px;margin:0 auto;border:1px solid #f0f1f3;border-radius:16px;padding:16px 18px;`
5386
5749
  );
5387
- const line = el$1("div", "display:flex;align-items:center;gap:8px;");
5750
+ const line = el("div", "display:flex;align-items:center;gap:8px;");
5388
5751
  const avg = data.aggregate.avg_rating ?? 0;
5389
5752
  line.appendChild(stars(Math.round(avg), 16));
5390
- line.appendChild(el$1("span", "font-size:16px;font-weight:700;", avg ? avg.toFixed(1) : "—"));
5391
- line.appendChild(el$1("span", "font-size:13px;color:#6b7280;", `(${data.aggregate.count})`));
5753
+ line.appendChild(el("span", "font-size:16px;font-weight:700;", avg ? avg.toFixed(1) : "—"));
5754
+ line.appendChild(el("span", "font-size:13px;color:#6b7280;", `(${data.aggregate.count})`));
5392
5755
  line.appendChild(verifiedBadge());
5393
5756
  root.appendChild(line);
5394
5757
  const top = data.items.find((i) => i.review_text);
5395
5758
  if (top && top.review_text) {
5396
- root.appendChild(el$1("p", "margin:10px 0 0;font-size:15px;color:#374151;line-height:1.5;", `“${top.review_text}”`));
5397
- root.appendChild(el$1("span", "font-size:13px;color:#9ca3af;", `— ${((_a = top.reviewer_snapshot) == null ? void 0 : _a.name) ?? "Customer"}`));
5759
+ root.appendChild(el("p", "margin:10px 0 0;font-size:15px;color:#374151;line-height:1.5;", `“${top.review_text}”`));
5760
+ root.appendChild(el("span", "font-size:13px;color:#9ca3af;", `— ${((_a = top.reviewer_snapshot) == null ? void 0 : _a.name) ?? "Customer"}`));
5398
5761
  }
5399
- const seeAll = el$1("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 →");
5762
+ 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 →");
5400
5763
  seeAll.addEventListener("click", () => renderWall(data));
5401
5764
  root.appendChild(seeAll);
5402
5765
  target.appendChild(root);
@@ -5420,10 +5783,10 @@ function renderReviewsOverlay(ctx, creds) {
5420
5783
  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;";
5421
5784
  const sheet = document.createElement("div");
5422
5785
  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);`;
5423
- sheet.appendChild(el$1("div", "width:38px;height:4px;border-radius:999px;background:#e5e7eb;margin:0 auto 14px;"));
5424
- const bar = el$1("div", "display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;");
5425
- bar.appendChild(el$1("div", "font-weight:800;font-size:17px;", ctx.campaign.title || "Customer reviews"));
5426
- const close = el$1("button", "background:none;border:none;cursor:pointer;padding:4px 8px;color:#6b7280;");
5786
+ sheet.appendChild(el("div", "width:38px;height:4px;border-radius:999px;background:#e5e7eb;margin:0 auto 14px;"));
5787
+ const bar = el("div", "display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;");
5788
+ bar.appendChild(el("div", "font-weight:800;font-size:17px;", ctx.campaign.title || "Customer reviews"));
5789
+ const close = el("button", "background:none;border:none;cursor:pointer;padding:4px 8px;color:#6b7280;");
5427
5790
  const x = document.createElementNS(SVG_NS, "svg");
5428
5791
  x.setAttribute("viewBox", "0 0 24 24");
5429
5792
  x.setAttribute("width", "20");
@@ -5442,7 +5805,7 @@ function renderReviewsOverlay(ctx, creds) {
5442
5805
  });
5443
5806
  bar.appendChild(close);
5444
5807
  sheet.appendChild(bar);
5445
- const body = el$1("div");
5808
+ const body = el("div");
5446
5809
  const pid = ctx.campaign.product_id;
5447
5810
  if (pid) body.setAttribute("data-product-id", pid);
5448
5811
  sheet.appendChild(body);
@@ -5456,161 +5819,6 @@ function renderReviewsOverlay(ctx, creds) {
5456
5819
  document.body.appendChild(overlay);
5457
5820
  renderReviewsInline(ctx, body, creds);
5458
5821
  }
5459
- const STAR_PATH = "M12 2L15.09 8.26 22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z";
5460
- function el(tag, css, txt) {
5461
- const e = document.createElement(tag);
5462
- e.style.cssText = css;
5463
- if (txt != null) e.textContent = txt;
5464
- return e;
5465
- }
5466
- function starSvg(filled, accent, size = 26) {
5467
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
5468
- svg.setAttribute("viewBox", "0 0 24 24");
5469
- svg.setAttribute("width", String(size));
5470
- svg.setAttribute("height", String(size));
5471
- svg.style.cssText = "display:block;transition:transform .1s ease;";
5472
- const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
5473
- path.setAttribute("d", STAR_PATH);
5474
- path.setAttribute("fill", "#d1d5db");
5475
- svg.appendChild(path);
5476
- return svg;
5477
- }
5478
- function readContext(target) {
5479
- const cfg = target.closest("[data-aegis-slot]") ?? target;
5480
- let items = [];
5481
- const raw = cfg.getAttribute("data-rateable-items");
5482
- if (raw) {
5483
- try {
5484
- const parsed = JSON.parse(raw);
5485
- if (Array.isArray(parsed)) {
5486
- items = parsed.filter((p) => !!p && typeof p === "object" && "product_id" in p).map((p) => ({ product_id: String(p.product_id), name: p.name ?? null }));
5487
- }
5488
- } catch {
5489
- }
5490
- }
5491
- return {
5492
- items,
5493
- orderId: cfg.getAttribute("data-order-id"),
5494
- billCode: cfg.getAttribute("data-bill-code"),
5495
- token: cfg.getAttribute("data-token"),
5496
- accent: cfg.getAttribute("data-accent"),
5497
- reviewsUrl: cfg.getAttribute("data-reviews-url"),
5498
- // The bill knows its customer server-side; the SDK may still be anonymous
5499
- // on a fresh visit. Prefer the slot-stamped contact so the per-item write
5500
- // (contact_id NOT NULL) succeeds. On untrusted origins the server ignores
5501
- // this and uses the token-resolved contact instead.
5502
- contactId: cfg.getAttribute("data-contact-id")
5503
- };
5504
- }
5505
- function renderProductRatingInline(ctx, target, creds) {
5506
- if (typeof document === "undefined" || !(creds == null ? void 0 : creds.apiHost) || !(creds == null ? void 0 : creds.writeKey)) return false;
5507
- ensureInAppFont();
5508
- const { items, orderId, billCode, token, accent: slotAccent, reviewsUrl, contactId: slotContact } = readContext(target);
5509
- if (items.length === 0 || !orderId && !billCode && !token) return false;
5510
- const ic = ctx.campaign.interactive_config || {};
5511
- const accent = ctx.sanitizeColor(
5512
- slotAccent || ctx.campaign.background_color || "#4169e1"
5513
- );
5514
- const title = ic.question || "Rate your items";
5515
- const scores = {};
5516
- target.innerHTML = "";
5517
- const card = el(
5518
- "div",
5519
- `font-family:${IN_APP_FONT_STACK};border:1px solid #f1f1f3;border-radius:14px;background:#fff;padding:14px 16px;`
5520
- );
5521
- card.appendChild(el("p", "margin:0;font-size:14px;font-weight:700;color:#111827;", title));
5522
- const list = el("ul", "list-style:none;margin:10px 0 0;padding:0;display:flex;flex-direction:column;gap:10px;");
5523
- for (const it of items) {
5524
- const li = el("li", "display:flex;align-items:center;justify-content:space-between;gap:12px;");
5525
- li.appendChild(el("span", "flex:1;min-width:0;font-size:13px;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", it.name || it.product_id));
5526
- const row = el("div", "display:flex;gap:2px;flex-shrink:0;");
5527
- const starEls = [];
5528
- for (let n = 1; n <= 5; n += 1) {
5529
- const btn = el("span", "cursor:pointer;padding:1px;line-height:0;");
5530
- const sv = starSvg();
5531
- btn.appendChild(sv);
5532
- starEls.push(sv);
5533
- btn.setAttribute("role", "button");
5534
- btn.setAttribute("aria-label", `Rate ${it.name || it.product_id} ${n} star${n === 1 ? "" : "s"}`);
5535
- btn.addEventListener("click", () => {
5536
- scores[it.product_id] = n;
5537
- starEls.forEach((s, i) => {
5538
- const p = s.querySelector("path");
5539
- if (p) p.setAttribute("fill", i < n ? accent : "#d1d5db");
5540
- });
5541
- submitBtn.style.opacity = "1";
5542
- submitBtn.style.pointerEvents = "auto";
5543
- });
5544
- row.appendChild(btn);
5545
- }
5546
- li.appendChild(row);
5547
- list.appendChild(li);
5548
- }
5549
- card.appendChild(list);
5550
- const submitBtn = el(
5551
- "button",
5552
- `margin-top:14px;width:100%;border:0;border-radius:999px;background:${accent};color:#fff;font-size:14px;font-weight:700;padding:10px 16px;cursor:pointer;opacity:.5;pointer-events:none;font-family:inherit;`,
5553
- "Submit ratings"
5554
- );
5555
- card.appendChild(submitBtn);
5556
- const done = (branch) => {
5557
- target.innerHTML = "";
5558
- const ack = el(
5559
- "div",
5560
- `font-family:${IN_APP_FONT_STACK};border:1px solid #f1f1f3;border-radius:14px;background:#fff;padding:18px 16px;text-align:center;`
5561
- );
5562
- ack.appendChild(el("p", "margin:0;font-size:14px;font-weight:700;color:#111827;", "Thanks for rating your items!"));
5563
- const positiveUrl = reviewsUrl || ic.rating_high_url || null;
5564
- if (branch === "positive" && positiveUrl) {
5565
- const a = document.createElement("a");
5566
- a.href = positiveUrl;
5567
- a.textContent = ic.rating_high_cta || "Share a review";
5568
- a.style.cssText = `display:inline-block;margin-top:8px;font-size:12px;font-weight:700;color:${accent};text-decoration:none;`;
5569
- ack.appendChild(a);
5570
- } else {
5571
- ack.appendChild(el("p", "margin:6px 0 0;font-size:12px;color:#9ca3af;", "We'll use your feedback to improve."));
5572
- }
5573
- target.appendChild(ack);
5574
- };
5575
- submitBtn.addEventListener("click", () => {
5576
- const rated = Object.keys(scores);
5577
- if (rated.length === 0) return;
5578
- submitBtn.disabled = true;
5579
- submitBtn.style.opacity = ".6";
5580
- submitBtn.textContent = "Saving…";
5581
- const payload = {
5582
- items: rated.map((pid) => ({ product_id: pid, score: scores[pid] })),
5583
- order_id: orderId,
5584
- bill_code: billCode,
5585
- token
5586
- };
5587
- fetch(new URL("/v1/in-app/responses", creds.apiHost).toString(), {
5588
- method: "POST",
5589
- headers: {
5590
- "Content-Type": "application/json",
5591
- "X-Aegis-Write-Key": creds.writeKey,
5592
- "X-Organization-ID": creds.organizationId
5593
- },
5594
- body: JSON.stringify({
5595
- campaign_id: ctx.campaign.id,
5596
- response_type: "rating",
5597
- contact_id: slotContact || creds.contactId,
5598
- platform: "web",
5599
- payload
5600
- })
5601
- }).then((r) => r.ok ? r.json() : null).then((data) => {
5602
- var _a;
5603
- ctx.trackEvent(ctx.campaign.id, "clicked");
5604
- done(((_a = data == null ? void 0 : data.rating_result) == null ? void 0 : _a.branch) || "positive");
5605
- }).catch(() => {
5606
- submitBtn.disabled = false;
5607
- submitBtn.style.opacity = "1";
5608
- submitBtn.textContent = "Submit ratings";
5609
- });
5610
- });
5611
- target.appendChild(card);
5612
- return true;
5613
- }
5614
5822
  function readAnonIdFromStorage$1() {
5615
5823
  if (typeof document === "undefined") return void 0;
5616
5824
  try {
@@ -5987,6 +6195,7 @@ const _AegisInAppManager = class _AegisInAppManager {
5987
6195
  return;
5988
6196
  }
5989
6197
  const url = new URL("/v1/stream/realtime", this.apiHost);
6198
+ url.searchParams.set("org", this.organizationId);
5990
6199
  const headers = {
5991
6200
  "X-Aegis-Write-Key": this.writeKey,
5992
6201
  "X-Organization-ID": this.organizationId
@@ -5994,11 +6203,10 @@ const _AegisInAppManager = class _AegisInAppManager {
5994
6203
  if (this.contactId) {
5995
6204
  headers["X-Contact-ID"] = this.contactId;
5996
6205
  }
5997
- const queryParams = new URLSearchParams();
5998
6206
  Object.entries(headers).forEach(([key, value]) => {
5999
- queryParams.append(key, value);
6207
+ url.searchParams.append(key, value);
6000
6208
  });
6001
- this.eventSource = new EventSource(`${url}?${queryParams.toString()}`);
6209
+ this.eventSource = new EventSource(url.toString());
6002
6210
  this.eventSource.addEventListener("open", () => {
6003
6211
  this.log("SSE connection established");
6004
6212
  this.reconnectAttempts = 0;
@@ -6161,7 +6369,7 @@ const _AegisInAppManager = class _AegisInAppManager {
6161
6369
  }
6162
6370
  tryDisplayNextCampaign() {
6163
6371
  const campaign = this.campaigns.find(
6164
- (c) => !this.displayedCampaigns.has(c.id) && !c.client_trigger && !this.isSuppressed(c.id) && c.type !== "active_web_chat"
6372
+ (c) => !this.displayedCampaigns.has(c.id) && !c.client_trigger && !this.isSuppressed(c.id) && c.type !== "active_web_chat" && !(Array.isArray(c.delivery_modes) && c.delivery_modes.length > 0 && !c.delivery_modes.includes("in_app_overlay"))
6165
6373
  );
6166
6374
  if (campaign) {
6167
6375
  this.displayCampaign(campaign);
@@ -6194,34 +6402,38 @@ const _AegisInAppManager = class _AegisInAppManager {
6194
6402
  * `<div data-aegis-slot="feedback">` will accept the first active
6195
6403
  * campaign with `widget_category='feedback'` + embedded_card mode.
6196
6404
  */
6197
- /**
6198
- * Surface predicate (In-App Surface Filter tracker P2). Byte-for-byte the
6199
- * same logic as the server-side `_surface_matches` in
6200
- * apps/cell-plane/app/api/v1/in_app/campaigns.py:
6201
- * - campaign with no `surface` (undefined / []) renders on every surface
6202
- * - no declared `currentSurface` => no filter (back-compat)
6203
- * - otherwise the current surface must be in the campaign's surface array
6204
- */
6205
- matchesCurrentSurface(c) {
6206
- if (!c.surface || c.surface.length === 0) return true;
6207
- if (this.currentSurface === null) return true;
6208
- return c.surface.includes(this.currentSurface);
6405
+ /** Normalize a declared free-form screen name to a canonical surface key
6406
+ * ('storefront.catalog' → 'storefront_catalog'), or null when it maps
6407
+ * outside the enum (e.g. 'shopify.product'). Mirrors the server's
6408
+ * `_canonical_surface_from_screen`. */
6409
+ canonicalSurfaceFromScreen() {
6410
+ if (!this.currentScreen) return null;
6411
+ const norm = this.currentScreen.trim().toLowerCase().replace(/[.\- ]/g, "_");
6412
+ return _AegisInAppManager.KNOWN_SURFACES.has(norm) ? norm : null;
6209
6413
  }
6210
6414
  /**
6211
- * Dynamic Screens P2 — combined surface + free-form screen predicate.
6212
- * Byte-for-byte the server's `_screen_or_surface_matches`: when the campaign
6213
- * has no `target_screens` this is exactly `matchesCurrentSurface` (every
6214
- * existing campaign → zero behaviour change). A screen-only campaign matches
6215
- * strictly on `currentScreen`; a dual campaign ORs the two tiers.
6415
+ * Dynamic Screens P2 + substrate isolation — combined surface + free-form
6416
+ * screen predicate, mirroring the server's `_screen_or_surface_matches`:
6417
+ * - screen-only campaign matches strictly on `currentScreen`
6418
+ * - a declared screen NORMALIZES into the canonical tier
6419
+ * ('storefront.catalog' → storefront_catalog) so surface pins match
6420
+ * - a declared screen that maps OUTSIDE the enum (e.g. 'shopify.product')
6421
+ * does NOT fall through to the all-surfaces default for surface-pinned
6422
+ * campaigns — actii.me-pinned campaigns never render on Shopify
6423
+ * - hosts that declare nothing keep the legacy render-everything default
6216
6424
  */
6217
6425
  matchesCurrentScreenOrSurface(c) {
6218
6426
  const screens = c.target_screens;
6219
- if (!screens || screens.length === 0) {
6220
- return this.matchesCurrentSurface(c);
6427
+ const screenOk = !!screens && screens.length > 0 && this.currentScreen !== null && screens.includes(this.currentScreen);
6428
+ if (screens && screens.length > 0 && (!c.surface || c.surface.length === 0)) {
6429
+ return screenOk;
6221
6430
  }
6222
- const screenOk = this.currentScreen !== null && screens.includes(this.currentScreen);
6223
- if (!c.surface || c.surface.length === 0) return screenOk;
6224
- return screenOk || this.matchesCurrentSurface(c);
6431
+ if (screenOk) return true;
6432
+ if (!c.surface || c.surface.length === 0) return true;
6433
+ const effective = this.currentSurface ?? this.canonicalSurfaceFromScreen();
6434
+ if (effective !== null) return c.surface.includes(effective);
6435
+ if (this.currentScreen !== null) return false;
6436
+ return true;
6225
6437
  }
6226
6438
  // Re-fill slots that mount after a poll (SPA route changes). Observes the DOM
6227
6439
  // once; when a new [data-aegis-slot] appears it re-runs renderIntoSlots from
@@ -6257,6 +6469,7 @@ const _AegisInAppManager = class _AegisInAppManager {
6257
6469
  this.slotObserver.observe(document.body, { childList: true, subtree: true });
6258
6470
  }
6259
6471
  renderIntoSlots() {
6472
+ var _a, _b;
6260
6473
  if (typeof document === "undefined") return;
6261
6474
  this.ensureSlotObserver();
6262
6475
  const slots = document.querySelectorAll("[data-aegis-slot]");
@@ -6269,6 +6482,16 @@ const _AegisInAppManager = class _AegisInAppManager {
6269
6482
  const category = c.widget_category;
6270
6483
  if (!modes || !modes.includes("embedded_card")) continue;
6271
6484
  if (!category) continue;
6485
+ const anchorSel = (_b = (_a = c.interactive_config) == null ? void 0 : _a.anchor) == null ? void 0 : _b.element_selector;
6486
+ if (typeof anchorSel === "string" && anchorSel.trim()) {
6487
+ let anchorResolves = false;
6488
+ try {
6489
+ anchorResolves = !!document.querySelector(anchorSel);
6490
+ } catch {
6491
+ anchorResolves = false;
6492
+ }
6493
+ if (anchorResolves) continue;
6494
+ }
6272
6495
  if (seenIds.has(c.id)) continue;
6273
6496
  seenIds.add(c.id);
6274
6497
  const arr2 = eligibleByCategory.get(category);
@@ -6295,8 +6518,8 @@ const _AegisInAppManager = class _AegisInAppManager {
6295
6518
  slot.querySelectorAll(":scope > [data-aegis-slot-default]").forEach((d) => d.remove());
6296
6519
  const attrMs = parseInt(slot.getAttribute("data-aegis-slot-rotate") || "0", 10);
6297
6520
  const declaredMs = list.reduce((m2, c) => {
6298
- var _a;
6299
- const v = Number((_a = c.interactive_config) == null ? void 0 : _a.slot_rotate_ms);
6521
+ var _a2;
6522
+ const v = Number((_a2 = c.interactive_config) == null ? void 0 : _a2.slot_rotate_ms);
6300
6523
  return Number.isFinite(v) && v > m2 ? v : m2;
6301
6524
  }, 0);
6302
6525
  const rotateMs = attrMs > 0 ? attrMs : declaredMs;
@@ -6582,23 +6805,6 @@ const _AegisInAppManager = class _AegisInAppManager {
6582
6805
  }
6583
6806
  return;
6584
6807
  }
6585
- if (campaign.sub_type === "star_rating" && this._isPerItemRating(campaign)) {
6586
- const ok = renderProductRatingInline(this.buildRenderContext(campaign), target, {
6587
- apiHost: this.apiHost,
6588
- writeKey: this.writeKey,
6589
- organizationId: this.organizationId ?? "",
6590
- contactId: this.contactId ?? null
6591
- });
6592
- if (!ok) {
6593
- this.displayedCampaigns.delete(campaign.id);
6594
- return;
6595
- }
6596
- if (!(options == null ? void 0 : options.deferImpression)) {
6597
- this.trackEvent(campaign.id, "impression");
6598
- this.emit("campaign-shown", campaign);
6599
- }
6600
- return;
6601
- }
6602
6808
  this._ensureSurveyTemplate(campaign);
6603
6809
  const _isMultiCard = campaign.sub_type === "product_recommendation" || campaign.sub_type === "carousel_cards";
6604
6810
  if (this._hasComposedTemplate(ic) && !_isMultiCard) {
@@ -6679,14 +6885,6 @@ const _AegisInAppManager = class _AegisInAppManager {
6679
6885
  const tpl = ic.card_template;
6680
6886
  return !!tpl && typeof tpl === "object" && Array.isArray(tpl.elements) && tpl.elements.length > 0;
6681
6887
  }
6682
- /** G5 — whether a `star_rating` campaign captures PER-SKU ratings (one row per
6683
- * order line-item) vs a single order-level score. Opt-in via
6684
- * `interactive_config.rating_mode='per_item'`, so the order-level star path is
6685
- * untouched. The renderer reads the SKUs from the slot at mount. */
6686
- _isPerItemRating(campaign) {
6687
- const ic = campaign.interactive_config || {};
6688
- return ic.rating_mode === "per_item";
6689
- }
6690
6888
  /**
6691
6889
  * P6.full — flat survey → card_template adapter. A `nps_survey` / `star_rating`
6692
6890
  * / `quick_poll` authored WITHOUT a card_template gets one synthesised in place
@@ -6756,9 +6954,11 @@ const _AegisInAppManager = class _AegisInAppManager {
6756
6954
  };
6757
6955
  const template = resolveGlobalTemplate(ic);
6758
6956
  const { accent } = this._surfacePalette(campaign);
6957
+ const ctx = this.buildRenderContext(campaign);
6958
+ ctx.slotEl = target.closest("[data-aegis-slot]") ?? target;
6759
6959
  const tile = renderCardFromTemplate(card, {
6760
6960
  template,
6761
- ctx: this.buildRenderContext(campaign),
6961
+ ctx,
6762
6962
  action: "navigate",
6763
6963
  layoutAccent: this.sanitizeColor(campaign.background_color || accent)
6764
6964
  });
@@ -8604,7 +8804,10 @@ const _AegisInAppManager = class _AegisInAppManager {
8604
8804
  },
8605
8805
  sanitizeColor: (color) => this.sanitizeColor(color),
8606
8806
  log: (msg, level) => this.log(msg, level),
8607
- addAnimationStyles: () => this.addAnimationStyles()
8807
+ addAnimationStyles: () => this.addAnimationStyles(),
8808
+ apiHost: this.apiHost,
8809
+ writeKey: this.writeKey,
8810
+ organizationId: this.organizationId
8608
8811
  };
8609
8812
  }
8610
8813
  /**
@@ -9009,6 +9212,12 @@ const _AegisInAppManager = class _AegisInAppManager {
9009
9212
  * so journeys can react. Degrades gracefully where Notification is absent.
9010
9213
  */
9011
9214
  renderPushPrimer(campaign, ic, bg, text) {
9215
+ if (typeof Notification !== "undefined" && Notification.permission !== "default") {
9216
+ void this.trackEvent(campaign.id, "dismissed", {
9217
+ meta: { reason: `permission_${Notification.permission}` }
9218
+ });
9219
+ return;
9220
+ }
9012
9221
  const overlay = this.createOverlay("aegis-in-app-push-primer-overlay");
9013
9222
  const modal = document.createElement("div");
9014
9223
  modal.style.cssText = `
@@ -9019,8 +9228,13 @@ const _AegisInAppManager = class _AegisInAppManager {
9019
9228
  const body = document.createElement("div");
9020
9229
  body.style.cssText = "padding: 24px; text-align: center;";
9021
9230
  const icon = document.createElement("div");
9022
- icon.style.cssText = "font-size: 36px; margin-bottom: 8px;";
9023
- icon.textContent = ic.icon || "🔔";
9231
+ icon.style.cssText = "margin-bottom: 8px; display: flex; justify-content: center;";
9232
+ if (typeof ic.icon === "string" && ic.icon.length > 0) {
9233
+ icon.textContent = ic.icon;
9234
+ icon.style.fontSize = "36px";
9235
+ } else {
9236
+ icon.innerHTML = lucideSvg("bell", { size: 36, color: text });
9237
+ }
9024
9238
  body.appendChild(icon);
9025
9239
  const title = document.createElement("div");
9026
9240
  title.style.cssText = "font-size: 18px; font-weight: 700; margin-bottom: 8px;";
@@ -9040,7 +9254,8 @@ const _AegisInAppManager = class _AegisInAppManager {
9040
9254
  if (overlay.parentNode) document.body.removeChild(overlay);
9041
9255
  return;
9042
9256
  }
9043
- void Promise.resolve(Notification.requestPermission()).then((perm) => {
9257
+ enableBtn.disabled = true;
9258
+ void this._subscribeFromPushPrimer().then((perm) => {
9044
9259
  void this.trackEvent(campaign.id, "clicked", { meta: { permission: perm } });
9045
9260
  if (overlay.parentNode) document.body.removeChild(overlay);
9046
9261
  });
@@ -9059,6 +9274,90 @@ const _AegisInAppManager = class _AegisInAppManager {
9059
9274
  this.addAnimationStyles();
9060
9275
  document.body.appendChild(overlay);
9061
9276
  }
9277
+ /**
9278
+ * Push-primer Enable handler — runs the SAME end-to-end subscribe flow the
9279
+ * (now-retired) ACS PushOptInBanner did, so the primer is a complete web-push
9280
+ * opt-in on every surface (ACS / Shopify / 3rd-party site), not just an OS
9281
+ * permission prompt. Steps: fetch VAPID public key → register SW → hard-ask →
9282
+ * subscribe → POST /v1/web_push/subscriptions → stitch known contact.
9283
+ *
9284
+ * Push runtime resolution prefers the already-loaded `window.AegisWebPush`
9285
+ * global (the Shopify embed lazy-loads it via /apps/active_reach/push.js, so
9286
+ * we reuse that single instance instead of registering the SW twice), and
9287
+ * only dynamic-imports the module as a fallback for hosts that didn't preload
9288
+ * it (ACS / 3rd-party sites). Returns the resolved permission string for
9289
+ * engagement reporting; falls back to a bare permission ask if push can't be
9290
+ * wired (no propertyId, no VAPID).
9291
+ */
9292
+ async _subscribeFromPushPrimer() {
9293
+ var _a, _b, _c, _d;
9294
+ if (!this.propertyId) {
9295
+ return Notification.requestPermission();
9296
+ }
9297
+ try {
9298
+ const cfgRes = await fetch(`${this.apiHost}/v1/sdk/config`, {
9299
+ headers: { "X-Aegis-Write-Key": this.writeKey }
9300
+ });
9301
+ const cfg = await cfgRes.json();
9302
+ const vapidPublicKey = ((_a = cfg.push) == null ? void 0 : _a.vapid_public_key) || "";
9303
+ if (!((_b = cfg.push) == null ? void 0 : _b.enabled) || !vapidPublicKey) {
9304
+ return Notification.requestPermission();
9305
+ }
9306
+ const serviceWorkerPath = ((_c = cfg.push) == null ? void 0 : _c.service_worker_path) || "/aegis-sw.js";
9307
+ const serviceWorkerScope = ((_d = cfg.push) == null ? void 0 : _d.service_worker_scope) || "/";
9308
+ if (this.contactId && typeof window !== "undefined") {
9309
+ try {
9310
+ window.localStorage.setItem("aegis_contact_id", this.contactId);
9311
+ } catch {
9312
+ }
9313
+ }
9314
+ const writeKey = this.writeKey;
9315
+ const apiHost = this.apiHost;
9316
+ const organizationId = this.organizationId;
9317
+ const apiClient = {
9318
+ async post(endpoint, payload, headers) {
9319
+ const res = await fetch(`${apiHost}${endpoint}`, {
9320
+ method: "POST",
9321
+ headers: {
9322
+ "Content-Type": "application/json",
9323
+ "X-Aegis-Write-Key": writeKey,
9324
+ ...organizationId ? { "X-Organization-ID": organizationId } : {},
9325
+ ...headers ?? {}
9326
+ },
9327
+ body: JSON.stringify(payload)
9328
+ });
9329
+ if (!res.ok) throw new Error(`push api ${endpoint} → ${res.status}`);
9330
+ return res.json().catch(() => ({}));
9331
+ }
9332
+ };
9333
+ const AegisWebPush2 = typeof window !== "undefined" && window.AegisWebPush || (await import("./push/AegisWebPush.js")).AegisWebPush;
9334
+ const webPush = new AegisWebPush2(apiClient, {
9335
+ vapidPublicKey,
9336
+ propertyId: this.propertyId,
9337
+ // Anon id doubles as the device-fingerprint salt so a re-subscribe
9338
+ // after VAPID rotation upserts onto the same subscription row.
9339
+ firstPartyCookieId: readAnonIdFromStorage$1() || this.contactId || this.organizationId || "",
9340
+ serviceWorkerPath,
9341
+ serviceWorkerScope,
9342
+ autoPrompt: false,
9343
+ writeKey,
9344
+ organizationId
9345
+ });
9346
+ await webPush.initialize();
9347
+ const granted = await webPush.requestPermission();
9348
+ if (granted && this.contactId) {
9349
+ await webPush.identify({ contactId: this.contactId });
9350
+ }
9351
+ return Notification.permission;
9352
+ } catch (err) {
9353
+ console.warn("[Aegis in-app] push-primer subscribe failed:", err);
9354
+ try {
9355
+ return await Notification.requestPermission();
9356
+ } catch {
9357
+ return Notification.permission;
9358
+ }
9359
+ }
9360
+ }
9062
9361
  renderQuiz(campaign, ic, bg, text) {
9063
9362
  const overlay = this.createOverlay("aegis-in-app-quiz-overlay");
9064
9363
  const modal = document.createElement("div");
@@ -11088,7 +11387,7 @@ const _AegisInAppManager = class _AegisInAppManager {
11088
11387
  }
11089
11388
  }
11090
11389
  try {
11091
- const url = `${this.apiHost}/v1/in_app/events`;
11390
+ const url = `${this.apiHost}/v1/in-app/events`;
11092
11391
  const externalEventId = `inapp_${campaignId}_${eventType}_${Date.now()}`;
11093
11392
  const headers = {
11094
11393
  "Content-Type": "application/json",
@@ -11122,12 +11421,16 @@ const _AegisInAppManager = class _AegisInAppManager {
11122
11421
  // journey/template/outlet on the engagement delivery_event.
11123
11422
  journey_execution_id: trackedCampaign == null ? void 0 : trackedCampaign.journey_execution_id,
11124
11423
  lifecycle_fork_id: trackedCampaign == null ? void 0 : trackedCampaign.lifecycle_fork_id,
11125
- location_id: trackedCampaign == null ? void 0 : trackedCampaign.location_id,
11424
+ // Outlet the customer is ACTIVELY viewing (setLocation) wins — that's
11425
+ // where the impression/click actually happened; fall back to the served
11426
+ // campaign's outlet, else brand-tier. The gateway promotes this to the
11427
+ // canonical event's top-level for per-outlet in-app analytics.
11428
+ location_id: this.currentLocationId ?? (trackedCampaign == null ? void 0 : trackedCampaign.location_id),
11126
11429
  metadata: {
11127
11430
  property_id: this.propertyId,
11128
11431
  variant_id: this.getVariantId(campaignId) ?? void 0,
11129
11432
  lifecycle_fork_id: trackedCampaign == null ? void 0 : trackedCampaign.lifecycle_fork_id,
11130
- location_id: trackedCampaign == null ? void 0 : trackedCampaign.location_id,
11433
+ location_id: this.currentLocationId ?? (trackedCampaign == null ? void 0 : trackedCampaign.location_id),
11131
11434
  // Renderer-supplied metadata (e.g. anchor_resolved/anchor_selector on
11132
11435
  // an anchor_unresolved event). The gateway spreads payload.metadata
11133
11436
  // into the canonical event so these land in in_app_events.metadata.
@@ -11190,6 +11493,7 @@ _AegisInAppManager.KNOWN_SURFACES = /* @__PURE__ */ new Set([
11190
11493
  "storefront_feedback",
11191
11494
  "storefront_reviews",
11192
11495
  "storefront_members",
11496
+ "storefront_orders",
11193
11497
  "external_website",
11194
11498
  "external_mobile_screen"
11195
11499
  ]);
@@ -14417,7 +14721,7 @@ const _AegisMessageRuntime = class _AegisMessageRuntime {
14417
14721
  var _a;
14418
14722
  this.initialized = false;
14419
14723
  {
14420
- const v = "1.35.0";
14724
+ const v = "1.35.4";
14421
14725
  try {
14422
14726
  globalThis.__AEGIS_SDK_VERSION__ = v;
14423
14727
  } catch {