@neowhale/storefront 0.2.33 → 0.2.35

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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  var chunkUW2U5BRY_cjs = require('../chunk-UW2U5BRY.cjs');
4
4
  var chunkHSGM4XMK_cjs = require('../chunk-HSGM4XMK.cjs');
5
+ var chunkHOQKBNYB_cjs = require('../chunk-HOQKBNYB.cjs');
5
6
  var react = require('react');
6
7
  var navigation = require('next/navigation');
7
8
  var vanilla = require('zustand/vanilla');
@@ -685,334 +686,6 @@ function PixelInitializer({ onReady, onTheme }) {
685
686
  }, [ctx, onReady, onTheme]);
686
687
  return null;
687
688
  }
688
-
689
- // src/behavioral/tracker.ts
690
- var SCROLL_MILESTONES = [25, 50, 75, 100];
691
- var TIME_MILESTONES = [30, 60, 120, 300];
692
- var MOUSE_THROTTLE_MS = 200;
693
- var MOUSE_BUFFER_MAX = 100;
694
- var RAGE_CLICK_COUNT = 3;
695
- var RAGE_CLICK_RADIUS = 50;
696
- var RAGE_CLICK_WINDOW_MS = 2e3;
697
- var MAX_CLICK_HISTORY = 10;
698
- var BehavioralTracker = class {
699
- constructor(config) {
700
- this.buffer = [];
701
- this.pageUrl = "";
702
- this.pagePath = "";
703
- this.flushTimer = null;
704
- this.scrollMilestones = /* @__PURE__ */ new Set();
705
- this.timeMilestones = /* @__PURE__ */ new Set();
706
- this.timeTimers = [];
707
- this.exitIntentFired = false;
708
- this.startTime = 0;
709
- this.clickHistory = [];
710
- this.mouseBuffer = [];
711
- this.lastMouseTime = 0;
712
- this.listeners = [];
713
- this.observer = null;
714
- this.sentinels = [];
715
- // ---------------------------------------------------------------------------
716
- // Event handlers (arrow functions for stable `this`)
717
- // ---------------------------------------------------------------------------
718
- this.handleClick = (e) => {
719
- const me = e;
720
- const target = me.target;
721
- if (!target) return;
722
- const now2 = Date.now();
723
- const x = me.clientX;
724
- const y = me.clientY;
725
- this.clickHistory.push({ x, y, t: now2 });
726
- if (this.clickHistory.length > MAX_CLICK_HISTORY) {
727
- this.clickHistory.shift();
728
- }
729
- const tag = target.tagName?.toLowerCase() ?? "";
730
- const rawText = target.textContent ?? "";
731
- const text = rawText.trim().slice(0, 50);
732
- this.push({
733
- data_type: "click",
734
- data: {
735
- tag,
736
- text,
737
- selector: this.getSelector(target),
738
- x,
739
- y,
740
- timestamp: now2
741
- },
742
- page_url: this.pageUrl,
743
- page_path: this.pagePath
744
- });
745
- this.detectRageClick(x, y, now2);
746
- };
747
- this.handleMouseMove = (e) => {
748
- const me = e;
749
- const now2 = Date.now();
750
- if (now2 - this.lastMouseTime < MOUSE_THROTTLE_MS) return;
751
- this.lastMouseTime = now2;
752
- this.mouseBuffer.push({ x: me.clientX, y: me.clientY, t: now2 });
753
- if (this.mouseBuffer.length > MOUSE_BUFFER_MAX) {
754
- this.mouseBuffer.shift();
755
- }
756
- };
757
- this.handleMouseOut = (e) => {
758
- const me = e;
759
- if (this.exitIntentFired) return;
760
- if (me.clientY > 0) return;
761
- if (me.relatedTarget !== null) return;
762
- this.exitIntentFired = true;
763
- this.push({
764
- data_type: "exit_intent",
765
- data: {
766
- time_on_page_ms: Date.now() - this.startTime,
767
- timestamp: Date.now()
768
- },
769
- page_url: this.pageUrl,
770
- page_path: this.pagePath
771
- });
772
- };
773
- this.handleCopy = () => {
774
- const selection = window.getSelection();
775
- const length = selection?.toString().length ?? 0;
776
- this.push({
777
- data_type: "copy",
778
- data: {
779
- text_length: length,
780
- timestamp: Date.now()
781
- },
782
- page_url: this.pageUrl,
783
- page_path: this.pagePath
784
- });
785
- };
786
- this.handleVisibilityChange = () => {
787
- if (document.visibilityState !== "hidden") return;
788
- const timeSpent = Date.now() - this.startTime;
789
- this.push({
790
- data_type: "page_exit",
791
- data: {
792
- time_spent_ms: timeSpent,
793
- timestamp: Date.now()
794
- },
795
- page_url: this.pageUrl,
796
- page_path: this.pagePath
797
- });
798
- this.flushMouseBuffer();
799
- this.flush();
800
- };
801
- this.config = {
802
- sendBatch: config.sendBatch,
803
- sessionId: config.sessionId,
804
- visitorId: config.visitorId,
805
- flushIntervalMs: config.flushIntervalMs ?? 1e4,
806
- maxBufferSize: config.maxBufferSize ?? 500
807
- };
808
- }
809
- start() {
810
- this.startTime = Date.now();
811
- this.addListener(document, "click", this.handleClick);
812
- this.addListener(document, "mousemove", this.handleMouseMove);
813
- this.addListener(document, "mouseout", this.handleMouseOut);
814
- this.addListener(document, "copy", this.handleCopy);
815
- this.addListener(document, "visibilitychange", this.handleVisibilityChange);
816
- this.setupScrollTracking();
817
- this.setupTimeMilestones();
818
- this.flushTimer = setInterval(() => this.flush(), this.config.flushIntervalMs);
819
- }
820
- stop() {
821
- for (const [target, event, handler] of this.listeners) {
822
- target.removeEventListener(event, handler, { capture: true });
823
- }
824
- this.listeners = [];
825
- if (this.flushTimer !== null) {
826
- clearInterval(this.flushTimer);
827
- this.flushTimer = null;
828
- }
829
- this.clearTimeMilestones();
830
- this.cleanupScrollTracking();
831
- this.flushMouseBuffer();
832
- this.flush();
833
- }
834
- setPageContext(url, path) {
835
- this.flushMouseBuffer();
836
- this.flush();
837
- this.pageUrl = url;
838
- this.pagePath = path;
839
- this.scrollMilestones.clear();
840
- this.timeMilestones.clear();
841
- this.exitIntentFired = false;
842
- this.startTime = Date.now();
843
- this.clickHistory = [];
844
- this.clearTimeMilestones();
845
- this.cleanupScrollTracking();
846
- this.setupTimeMilestones();
847
- requestAnimationFrame(() => this.setupScrollTracking());
848
- }
849
- // ---------------------------------------------------------------------------
850
- // Buffer management
851
- // ---------------------------------------------------------------------------
852
- push(event) {
853
- this.buffer.push(event);
854
- if (this.buffer.length >= this.config.maxBufferSize) {
855
- this.flush();
856
- }
857
- }
858
- flush() {
859
- if (this.buffer.length === 0) return;
860
- const batch = {
861
- session_id: this.config.sessionId,
862
- visitor_id: this.config.visitorId,
863
- events: this.buffer
864
- };
865
- this.buffer = [];
866
- this.config.sendBatch(batch).catch(() => {
867
- });
868
- }
869
- addListener(target, event, handler) {
870
- target.addEventListener(event, handler, { passive: true, capture: true });
871
- this.listeners.push([target, event, handler]);
872
- }
873
- // ---------------------------------------------------------------------------
874
- // Scroll tracking with IntersectionObserver
875
- // ---------------------------------------------------------------------------
876
- setupScrollTracking() {
877
- if (typeof IntersectionObserver === "undefined") return;
878
- this.observer = new IntersectionObserver(
879
- (entries) => {
880
- for (const entry of entries) {
881
- if (!entry.isIntersecting) continue;
882
- const milestone = Number(entry.target.getAttribute("data-scroll-milestone"));
883
- if (isNaN(milestone) || this.scrollMilestones.has(milestone)) continue;
884
- this.scrollMilestones.add(milestone);
885
- this.push({
886
- data_type: "scroll_depth",
887
- data: {
888
- depth_percent: milestone,
889
- timestamp: Date.now()
890
- },
891
- page_url: this.pageUrl,
892
- page_path: this.pagePath
893
- });
894
- }
895
- },
896
- { threshold: 0 }
897
- );
898
- const docHeight = document.documentElement.scrollHeight;
899
- for (const pct of SCROLL_MILESTONES) {
900
- const sentinel = document.createElement("div");
901
- sentinel.setAttribute("data-scroll-milestone", String(pct));
902
- sentinel.style.position = "absolute";
903
- sentinel.style.left = "0";
904
- sentinel.style.width = "1px";
905
- sentinel.style.height = "1px";
906
- sentinel.style.pointerEvents = "none";
907
- sentinel.style.opacity = "0";
908
- sentinel.style.top = `${docHeight * pct / 100 - 1}px`;
909
- document.body.appendChild(sentinel);
910
- this.sentinels.push(sentinel);
911
- this.observer.observe(sentinel);
912
- }
913
- }
914
- cleanupScrollTracking() {
915
- if (this.observer) {
916
- this.observer.disconnect();
917
- this.observer = null;
918
- }
919
- for (const sentinel of this.sentinels) {
920
- sentinel.remove();
921
- }
922
- this.sentinels = [];
923
- }
924
- // ---------------------------------------------------------------------------
925
- // Time milestones
926
- // ---------------------------------------------------------------------------
927
- setupTimeMilestones() {
928
- for (const seconds of TIME_MILESTONES) {
929
- const timer = setTimeout(() => {
930
- if (this.timeMilestones.has(seconds)) return;
931
- this.timeMilestones.add(seconds);
932
- this.push({
933
- data_type: "time_on_page",
934
- data: {
935
- milestone_seconds: seconds,
936
- timestamp: Date.now()
937
- },
938
- page_url: this.pageUrl,
939
- page_path: this.pagePath
940
- });
941
- }, seconds * 1e3);
942
- this.timeTimers.push(timer);
943
- }
944
- }
945
- clearTimeMilestones() {
946
- for (const timer of this.timeTimers) {
947
- clearTimeout(timer);
948
- }
949
- this.timeTimers = [];
950
- }
951
- // ---------------------------------------------------------------------------
952
- // Rage click detection
953
- // ---------------------------------------------------------------------------
954
- detectRageClick(x, y, now2) {
955
- const windowStart = now2 - RAGE_CLICK_WINDOW_MS;
956
- const nearby = this.clickHistory.filter((c) => {
957
- if (c.t < windowStart) return false;
958
- const dx = c.x - x;
959
- const dy = c.y - y;
960
- return Math.sqrt(dx * dx + dy * dy) <= RAGE_CLICK_RADIUS;
961
- });
962
- if (nearby.length >= RAGE_CLICK_COUNT) {
963
- this.push({
964
- data_type: "rage_click",
965
- data: {
966
- x,
967
- y,
968
- click_count: nearby.length,
969
- timestamp: now2
970
- },
971
- page_url: this.pageUrl,
972
- page_path: this.pagePath
973
- });
974
- this.clickHistory = [];
975
- }
976
- }
977
- // ---------------------------------------------------------------------------
978
- // Mouse buffer flush
979
- // ---------------------------------------------------------------------------
980
- flushMouseBuffer() {
981
- if (this.mouseBuffer.length === 0) return;
982
- this.push({
983
- data_type: "mouse_movement",
984
- data: {
985
- points: [...this.mouseBuffer],
986
- timestamp: Date.now()
987
- },
988
- page_url: this.pageUrl,
989
- page_path: this.pagePath
990
- });
991
- this.mouseBuffer = [];
992
- }
993
- // ---------------------------------------------------------------------------
994
- // CSS selector helper
995
- // ---------------------------------------------------------------------------
996
- getSelector(el) {
997
- const parts = [];
998
- let current = el;
999
- let depth = 0;
1000
- while (current && depth < 3) {
1001
- let segment = current.tagName.toLowerCase();
1002
- if (current.id) {
1003
- segment += `#${current.id}`;
1004
- } else if (current.classList.length > 0) {
1005
- segment += `.${Array.from(current.classList).join(".")}`;
1006
- }
1007
- parts.unshift(segment);
1008
- current = current.parentElement;
1009
- depth++;
1010
- }
1011
- return parts.join(" > ");
1012
- }
1013
- };
1014
-
1015
- // src/react/components/behavioral-tracker.tsx
1016
689
  var SESSION_KEY_SUFFIX2 = "-analytics-session";
1017
690
  var VISITOR_KEY_SUFFIX2 = "-visitor-id";
1018
691
  var MAX_SESSION_WAIT_MS = 1e4;
@@ -1073,7 +746,7 @@ function BehavioralTrackerComponent({ pathname }) {
1073
746
  "x-api-key": config.apiKey
1074
747
  });
1075
748
  };
1076
- const tracker = new BehavioralTracker({ sendBatch, sessionId, visitorId });
749
+ const tracker = new chunkHOQKBNYB_cjs.BehavioralTracker({ sendBatch, sessionId, visitorId });
1077
750
  tracker.start();
1078
751
  trackerRef.current = tracker;
1079
752
  };
@@ -2572,7 +2245,26 @@ function SectionRenderer({
2572
2245
  return null;
2573
2246
  }
2574
2247
  })();
2575
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2248
+ const sectionRef = react.useRef(null);
2249
+ react.useEffect(() => {
2250
+ const el2 = sectionRef.current;
2251
+ if (!el2 || typeof IntersectionObserver === "undefined") return;
2252
+ const obs = new IntersectionObserver(
2253
+ ([entry]) => {
2254
+ if (entry.isIntersecting) {
2255
+ onEvent?.("section_view", {
2256
+ section_id: section.id,
2257
+ section_type: section.type
2258
+ });
2259
+ obs.disconnect();
2260
+ }
2261
+ },
2262
+ { threshold: 0.5 }
2263
+ );
2264
+ obs.observe(el2);
2265
+ return () => obs.disconnect();
2266
+ }, [section.id, section.type, onEvent]);
2267
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: sectionRef, "data-section-id": section.id, "data-section-type": section.type, children: [
2576
2268
  el,
2577
2269
  showCOA && data?.coa && /* @__PURE__ */ jsxRuntime.jsx(COAModal, { coa: data.coa, theme, onClose: () => setShowCOA(false) })
2578
2270
  ] });
@@ -3479,7 +3171,8 @@ function LandingPage({
3479
3171
  onDataLoaded,
3480
3172
  onError,
3481
3173
  onEvent,
3482
- analyticsContext
3174
+ analyticsContext,
3175
+ enableAnalytics = true
3483
3176
  }) {
3484
3177
  const [state, setState] = react.useState("loading");
3485
3178
  const [data, setData] = react.useState(null);
@@ -3487,6 +3180,13 @@ function LandingPage({
3487
3180
  react.useEffect(() => {
3488
3181
  if (!slug) return;
3489
3182
  let cancelled = false;
3183
+ if (typeof window !== "undefined" && window.__LANDING_DATA__) {
3184
+ const json = window.__LANDING_DATA__;
3185
+ setData(json);
3186
+ setState("ready");
3187
+ onDataLoaded?.(json);
3188
+ return;
3189
+ }
3490
3190
  async function load() {
3491
3191
  try {
3492
3192
  const res = await fetch(`${gatewayUrl}/l/${encodeURIComponent(slug)}`);
@@ -3527,7 +3227,7 @@ function LandingPage({
3527
3227
  if (state === "expired") return /* @__PURE__ */ jsxRuntime.jsx(DefaultExpired2, {});
3528
3228
  if (state === "error") return /* @__PURE__ */ jsxRuntime.jsx(DefaultError2, { message: errorMsg });
3529
3229
  if (!data) return null;
3530
- return /* @__PURE__ */ jsxRuntime.jsx(PageLayout, { data, gatewayUrl, renderSection, onEvent, analyticsContext });
3230
+ return /* @__PURE__ */ jsxRuntime.jsx(PageLayout, { data, gatewayUrl, renderSection, onEvent, analyticsContext, enableAnalytics });
3531
3231
  }
3532
3232
  function isSectionVisible(section, urlParams) {
3533
3233
  const vis = section.config?.visibility;
@@ -3543,9 +3243,97 @@ function PageLayout({
3543
3243
  gatewayUrl,
3544
3244
  renderSection,
3545
3245
  onEvent,
3546
- analyticsContext
3246
+ analyticsContext,
3247
+ enableAnalytics
3547
3248
  }) {
3548
3249
  const { landing_page: lp, store } = data;
3250
+ const trackerRef = react.useRef(null);
3251
+ react.useEffect(() => {
3252
+ if (!enableAnalytics || typeof window === "undefined") return;
3253
+ const analyticsConfig = window.__LANDING_ANALYTICS__;
3254
+ if (!analyticsConfig?.slug) return;
3255
+ let visitorId = localStorage.getItem("wt_vid") || "";
3256
+ if (!visitorId) {
3257
+ visitorId = crypto.randomUUID();
3258
+ localStorage.setItem("wt_vid", visitorId);
3259
+ }
3260
+ let sessionId = sessionStorage.getItem("wt_sid") || "";
3261
+ if (!sessionId) {
3262
+ sessionId = crypto.randomUUID();
3263
+ sessionStorage.setItem("wt_sid", sessionId);
3264
+ }
3265
+ import('../tracker-WYKTEQ4F.cjs').then(({ BehavioralTracker: BehavioralTracker2 }) => {
3266
+ const gwUrl = analyticsConfig.gatewayUrl || gatewayUrl;
3267
+ const slug = analyticsConfig.slug;
3268
+ const utmParams = new URLSearchParams(window.location.search);
3269
+ const tracker = new BehavioralTracker2({
3270
+ sessionId,
3271
+ visitorId,
3272
+ sendBatch: async (batch) => {
3273
+ const events = batch.events.map((e) => ({
3274
+ event_type: e.data_type,
3275
+ event_data: e.data,
3276
+ session_id: batch.session_id,
3277
+ visitor_id: batch.visitor_id,
3278
+ campaign_id: analyticsConfig.campaignId || utmParams.get("utm_campaign_id") || void 0,
3279
+ utm_source: utmParams.get("utm_source") || void 0,
3280
+ utm_medium: utmParams.get("utm_medium") || void 0,
3281
+ utm_campaign: utmParams.get("utm_campaign") || void 0
3282
+ }));
3283
+ const body = JSON.stringify({ events });
3284
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
3285
+ navigator.sendBeacon(
3286
+ `${gwUrl}/l/${encodeURIComponent(slug)}/events`,
3287
+ new Blob([body], { type: "application/json" })
3288
+ );
3289
+ } else {
3290
+ await fetch(`${gwUrl}/l/${encodeURIComponent(slug)}/events`, {
3291
+ method: "POST",
3292
+ headers: { "Content-Type": "application/json" },
3293
+ body,
3294
+ keepalive: true
3295
+ });
3296
+ }
3297
+ }
3298
+ });
3299
+ tracker.setPageContext(window.location.href, window.location.pathname);
3300
+ tracker.start();
3301
+ trackerRef.current = tracker;
3302
+ const pageViewBody = JSON.stringify({
3303
+ events: [{
3304
+ event_type: "page_view",
3305
+ event_data: { referrer: document.referrer, url: window.location.href },
3306
+ session_id: sessionId,
3307
+ visitor_id: visitorId,
3308
+ campaign_id: analyticsConfig.campaignId || void 0,
3309
+ utm_source: utmParams.get("utm_source") || void 0,
3310
+ utm_medium: utmParams.get("utm_medium") || void 0,
3311
+ utm_campaign: utmParams.get("utm_campaign") || void 0
3312
+ }]
3313
+ });
3314
+ if (navigator.sendBeacon) {
3315
+ navigator.sendBeacon(
3316
+ `${gwUrl}/l/${encodeURIComponent(slug)}/events`,
3317
+ new Blob([pageViewBody], { type: "application/json" })
3318
+ );
3319
+ } else {
3320
+ fetch(`${gwUrl}/l/${encodeURIComponent(slug)}/events`, {
3321
+ method: "POST",
3322
+ headers: { "Content-Type": "application/json" },
3323
+ body: pageViewBody,
3324
+ keepalive: true
3325
+ }).catch(() => {
3326
+ });
3327
+ }
3328
+ }).catch(() => {
3329
+ });
3330
+ return () => {
3331
+ if (trackerRef.current) {
3332
+ trackerRef.current.stop();
3333
+ trackerRef.current = null;
3334
+ }
3335
+ };
3336
+ }, [enableAnalytics, gatewayUrl]);
3549
3337
  const theme = {
3550
3338
  bg: lp.background_color || store?.theme?.background || "#050505",
3551
3339
  fg: lp.text_color || store?.theme?.foreground || "#fafafa",