cryptique-sdk 1.2.16 → 1.2.18

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/lib/umd/index.js CHANGED
@@ -96,8 +96,8 @@
96
96
 
97
97
  // Geolocation API
98
98
  GEOLOCATION: {
99
- PRIMARY_URL: "https://ipinfo.io/json?token=73937f74acc045",
100
- BACKUP_URL: "https://ipinfo.io/json?token=73937f74acc045&http=1.1",
99
+ PRIMARY_URL: "https://ipinfo.io/json?token=8fc6409059aa39",
100
+ BACKUP_URL: "https://ipinfo.io/json?token=8fc6409059aa39&http=1.1",
101
101
  TIMEOUT_MS: 5000 // 5 second timeout
102
102
  },
103
103
 
@@ -5003,19 +5003,26 @@
5003
5003
  startCopyPasteTracking() {
5004
5004
  try {
5005
5005
  document.addEventListener('copy', (event) => {
5006
+ const sel = window.getSelection();
5006
5007
  const copyData = {
5007
5008
  type: 'copy_action',
5008
- selectedText: window.getSelection().toString().substring(0, 100),
5009
+ selectedText: sel ? sel.toString().substring(0, 100) : '',
5009
5010
  target: {
5010
5011
  tagName: event.target?.tagName || '',
5011
5012
  id: event.target?.id || ''
5012
5013
  },
5013
5014
  path: window.location.pathname
5014
5015
  };
5015
-
5016
5016
  InteractionManager.add('copyPasteEvents', copyData);
5017
+ // Fire structured auto-event (low volume — only when user explicitly copies)
5018
+ EventsManager.trackAutoEvent('copy_action', {
5019
+ text_length: sel ? sel.toString().length : 0,
5020
+ element_id: event.target?.id || '',
5021
+ element_tag: event.target?.tagName?.toLowerCase() || '',
5022
+ element_class: (event.target?.className || '').split(' ').filter(Boolean).slice(0, 3).join(' '),
5023
+ });
5017
5024
  });
5018
-
5025
+
5019
5026
  document.addEventListener('paste', (event) => {
5020
5027
  const pasteData = {
5021
5028
  type: 'paste_action',
@@ -5025,7 +5032,6 @@
5025
5032
  },
5026
5033
  path: window.location.pathname
5027
5034
  };
5028
-
5029
5035
  InteractionManager.add('copyPasteEvents', pasteData);
5030
5036
  });
5031
5037
  } catch (error) {
@@ -5051,8 +5057,15 @@
5051
5057
  },
5052
5058
  path: window.location.pathname
5053
5059
  };
5054
-
5055
5060
  InteractionManager.add('contextMenuEvents', contextData);
5061
+ // Fire structured auto-event (low volume — only when user right-clicks)
5062
+ EventsManager.trackAutoEvent('context_menu', {
5063
+ element_tag: event.target?.tagName?.toLowerCase() || '',
5064
+ element_id: event.target?.id || '',
5065
+ element_class: (event.target?.className || '').split(' ').filter(Boolean).slice(0, 3).join(' '),
5066
+ page_x: event.pageX,
5067
+ page_y: event.pageY,
5068
+ });
5056
5069
  });
5057
5070
  } catch (error) {
5058
5071
  }
@@ -5510,74 +5523,376 @@
5510
5523
 
5511
5524
  /**
5512
5525
  * ElementVisibilityTracker - Emits element_view auto events when elements
5513
- * with IDs or data-track attributes enter the viewport for ≥ 1 second.
5514
- * Tells PMs whether users actually SAW key elements (CTAs, pricing, etc.)
5515
- * vs just didn't click them completely different problems, opposite fixes.
5526
+ * enter the viewport for ≥ 1 second, capturing the actual dwell duration.
5527
+ *
5528
+ * Covers interactive elements (buttons, links, inputs, headings, images) and
5529
+ * any element with an id or data-cq-track attribute. Each viewport entry is
5530
+ * tracked independently — if a user scrolls away and back, both dwells count.
5531
+ * This gives accurate attention data for the heatmap attention overlay.
5516
5532
  */
5517
5533
  startElementVisibilityTracking() {
5518
5534
  try {
5519
5535
  if (typeof IntersectionObserver === 'undefined') return;
5520
5536
 
5521
- // Only observe elements with an id or data-cq-track attribute
5537
+ // Observe interactive/structural elements in addition to id/data-cq-track
5538
+ const TRACKABLE_SELECTOR = [
5539
+ '[id]:not([id=""])', '[data-cq-track]',
5540
+ 'button', 'a[href]', 'input:not([type=hidden])', 'select', 'textarea',
5541
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img[alt]',
5542
+ '[role="button"]', '[role="tab"]', '[role="menuitem"]', '[role="link"]'
5543
+ ].join(', ');
5544
+
5522
5545
  const getTrackableElements = () =>
5523
- document.querySelectorAll('[id]:not([id=""]), [data-cq-track]');
5546
+ Array.from(document.querySelectorAll(TRACKABLE_SELECTOR)).filter(el => {
5547
+ try { return el.getBoundingClientRect().height >= 20; } catch (_) { return true; }
5548
+ });
5524
5549
 
5525
- const viewTimers = new Map(); // elementKey setTimeout handle
5526
- const reported = new Set(); // elementKey → already fired once per page load
5550
+ // enterTimes: key { enterTime, el } tracks elements currently in viewport
5551
+ const enterTimes = new Map();
5552
+ const observedEls = new WeakSet(); // prevents double-observing same DOM node
5553
+
5554
+ function elementKey(el) {
5555
+ return `${el.tagName}|${el.id || ''}|${el.getAttribute('data-cq-track') || ''}|${(el.textContent || '').trim().slice(0, 30)}`;
5556
+ }
5557
+
5558
+ function getElementData(el) {
5559
+ return {
5560
+ element_id: el.id || '',
5561
+ element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
5562
+ element_tag_name: el.tagName || '',
5563
+ element_type: el.type || el.getAttribute('type') || '',
5564
+ element_class: el.className || '',
5565
+ element_text: (el.textContent || '').trim().slice(0, 100)
5566
+ };
5567
+ }
5527
5568
 
5528
5569
  const observer = new IntersectionObserver((entries) => {
5529
5570
  entries.forEach((entry) => {
5530
- const el = entry.target;
5531
- const key = el.id || el.getAttribute('data-cq-track') || el.className;
5532
- if (!key || reported.has(key)) return;
5571
+ const el = entry.target;
5572
+ const key = elementKey(el);
5533
5573
 
5534
5574
  if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
5535
- // Element entered viewport — start 1s timer
5536
- if (!viewTimers.has(key)) {
5537
- const enterTime = Date.now();
5538
- const timer = setTimeout(() => {
5539
- if (reported.has(key)) return;
5540
- reported.add(key);
5541
- EventsManager.trackAutoEvent('element_view', {
5542
- time_in_viewport_ms: Date.now() - enterTime,
5543
- viewport_percent_visible: Math.round(entry.intersectionRatio * 100)
5544
- }, {
5545
- element_id: el.id || '',
5546
- element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
5547
- element_tag_name: el.tagName || '',
5548
- element_type: el.type || el.getAttribute('type') || '',
5549
- element_class: el.className || '',
5550
- element_text: (el.textContent || '').trim().slice(0, 100)
5551
- });
5552
- viewTimers.delete(key);
5553
- }, 1000);
5554
- viewTimers.set(key, timer);
5575
+ // Element entered viewport — record entry time
5576
+ if (!enterTimes.has(key)) {
5577
+ enterTimes.set(key, { enterTime: Date.now(), el });
5555
5578
  }
5556
5579
  } else {
5557
- // Element left viewport before 1s cancel timer
5558
- if (viewTimers.has(key)) {
5559
- clearTimeout(viewTimers.get(key));
5560
- viewTimers.delete(key);
5580
+ // Element left viewport emit actual dwell if ≥ 1s
5581
+ const record = enterTimes.get(key);
5582
+ if (record) {
5583
+ const dwell = Date.now() - record.enterTime;
5584
+ enterTimes.delete(key);
5585
+ if (dwell >= 1000) {
5586
+ EventsManager.trackAutoEvent('element_view', {
5587
+ time_in_viewport_ms: dwell,
5588
+ viewport_percent_visible: Math.round(entry.intersectionRatio * 100),
5589
+ scroll_y_at_view: window.scrollY
5590
+ }, getElementData(el));
5591
+ }
5561
5592
  }
5562
5593
  }
5563
5594
  });
5564
- }, { threshold: 0.5 });
5595
+ }, { threshold: [0.5] });
5596
+
5597
+ const observe = (el) => {
5598
+ if (!observedEls.has(el)) { observedEls.add(el); observer.observe(el); }
5599
+ };
5565
5600
 
5566
- // Observe existing elements
5567
- getTrackableElements().forEach(el => observer.observe(el));
5601
+ getTrackableElements().forEach(observe);
5568
5602
 
5569
5603
  // Observe elements added to DOM later (SPAs)
5570
5604
  if (typeof MutationObserver !== 'undefined') {
5571
5605
  new MutationObserver(() => {
5572
- getTrackableElements().forEach(el => {
5573
- if (!viewTimers.has(el.id || el.getAttribute('data-cq-track') || el.className)) {
5574
- observer.observe(el);
5575
- }
5576
- });
5606
+ getTrackableElements().forEach(observe);
5577
5607
  }).observe(document.body, { childList: true, subtree: true });
5578
5608
  }
5579
- } catch (error) {
5609
+
5610
+ // On page unload, flush elements still in viewport
5611
+ window.addEventListener('pagehide', () => {
5612
+ enterTimes.forEach(({ enterTime, el }) => {
5613
+ const dwell = Date.now() - enterTime;
5614
+ if (dwell >= 1000) {
5615
+ EventsManager.trackAutoEvent('element_view', {
5616
+ time_in_viewport_ms: dwell,
5617
+ viewport_percent_visible: 100,
5618
+ scroll_y_at_view: window.scrollY
5619
+ }, getElementData(el));
5620
+ }
5621
+ });
5622
+ });
5623
+ } catch (error) {}
5624
+ },
5625
+
5626
+ /**
5627
+ * PageSummaryTracker — collects high-frequency signals in-memory and flushes
5628
+ * them as a single 'page_summary' auto-event on page unload.
5629
+ *
5630
+ * High-volume/continuous data lives here (grids, counts) to keep event volume low.
5631
+ * Low-frequency, high-signal moments (exit_intent) are emitted as separate auto-events
5632
+ * but their counters are also included here for correlation.
5633
+ *
5634
+ * Captures:
5635
+ * - Mouse movement grid (move_grid) + hover dwell grid (hover_grid)
5636
+ * - Element-level hover dwells for named/interactive elements (element_dwells)
5637
+ * - Tab-switch count and hidden time
5638
+ * - Scroll reversal count + depths at each reversal (scroll_reversal_depths)
5639
+ * - Per-field focus dwell times
5640
+ * - Exit intent count + scroll depth at last exit intent
5641
+ * - First interaction time (how long before user engaged)
5642
+ * - Performance: CLS score, long task count/max, INP
5643
+ */
5644
+ startPageSummaryTracking() {
5645
+ try {
5646
+ const GRID_SIZE = 40;
5647
+
5648
+ // In-memory accumulators — never sent to the server individually
5649
+ const moveGrid = new Map(); // cellKey → move count
5650
+ const hoverGrid = new Map(); // cellKey → total dwell_ms
5651
+ const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
5652
+ const elementDwellsMap = new Map(); // elemKey → {tag, id, text, dwell_ms}
5653
+ const scrollReversalDepths = []; // scroll depth % at each reversal
5654
+
5655
+ let tabSwitches = 0;
5656
+ let totalTabHiddenMs = 0;
5657
+ let tabHiddenTime = null;
5658
+ let scrollReversals = 0;
5659
+ let exitIntentCount = 0;
5660
+ let exitIntentLastScrollDepth = 0;
5661
+ let firstInteractionMs = null;
5662
+ let clsScore = 0;
5663
+ let longTasksCount = 0;
5664
+ let maxLongTaskMs = 0;
5665
+ let inpMs = 0;
5666
+
5667
+ const pageLoadTime = Date.now();
5668
+ let lastSigScrollY = window.scrollY;
5669
+ let lastSigDir = 0; // 1=down, -1=up
5670
+
5671
+ // ── Helpers
5672
+ function getScrollDepth() {
5673
+ const maxScroll = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - window.innerHeight;
5674
+ return maxScroll <= 0 ? 100 : Math.round((window.scrollY / maxScroll) * 100);
5675
+ }
5676
+
5677
+ function getGridCell(pageX, pageY) {
5678
+ const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
5679
+ const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
5680
+ const col = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageX / docW) * GRID_SIZE)));
5681
+ const row = Math.min(GRID_SIZE - 1, Math.max(0, Math.floor((pageY / docH) * GRID_SIZE)));
5682
+ return col * GRID_SIZE + row;
5683
+ }
5684
+
5685
+ // ── PerformanceObserver: CLS, Long Tasks, INP (best-effort, not all browsers)
5686
+ try {
5687
+ new PerformanceObserver(list => {
5688
+ for (const e of list.getEntries()) {
5689
+ if (!e.hadRecentInput) clsScore = Math.round((clsScore + e.value) * 1000) / 1000;
5690
+ }
5691
+ }).observe({ type: 'layout-shift', buffered: true });
5692
+ } catch (_) {}
5693
+ try {
5694
+ new PerformanceObserver(list => {
5695
+ for (const e of list.getEntries()) {
5696
+ longTasksCount++;
5697
+ if (e.duration > maxLongTaskMs) maxLongTaskMs = Math.round(e.duration);
5698
+ }
5699
+ }).observe({ type: 'longtask', buffered: true });
5700
+ } catch (_) {}
5701
+ try {
5702
+ new PerformanceObserver(list => {
5703
+ for (const e of list.getEntries()) {
5704
+ if (e.duration > inpMs) inpMs = Math.round(e.duration);
5705
+ }
5706
+ }).observe({ type: 'event', durationThreshold: 40, buffered: true });
5707
+ } catch (_) {}
5708
+
5709
+ // ── First interaction: time from page load to first meaningful engagement
5710
+ const markFirstInteraction = () => {
5711
+ if (firstInteractionMs === null) firstInteractionMs = Date.now() - pageLoadTime;
5712
+ };
5713
+ document.addEventListener('click', markFirstInteraction, { once: true, passive: true });
5714
+ document.addEventListener('keydown', markFirstInteraction, { once: true, passive: true });
5715
+ document.addEventListener('touchstart', markFirstInteraction, { once: true, passive: true });
5716
+ window.addEventListener('scroll', markFirstInteraction, { once: true, passive: true });
5717
+
5718
+ // ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
5719
+ let moveThrottle = false;
5720
+ document.addEventListener('mousemove', (e) => {
5721
+ if (moveThrottle) return;
5722
+ moveThrottle = true;
5723
+ setTimeout(() => { moveThrottle = false; }, 100);
5724
+ const key = getGridCell(e.pageX, e.pageY);
5725
+ moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
5726
+ }, { passive: true });
5727
+
5728
+ // ── Hover dwell → hover_grid + element_dwells for named/interactive elements
5729
+ const ELEMENT_DWELL_TAGS = new Set(['BUTTON', 'A', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'INPUT', 'SELECT', 'TEXTAREA', 'IMG', 'LABEL']);
5730
+ const hoverStarts = new Map(); // element → enterTime
5731
+ document.addEventListener('mouseover', (e) => {
5732
+ if (e.target && e.target !== document.documentElement) {
5733
+ hoverStarts.set(e.target, Date.now());
5734
+ }
5735
+ }, { passive: true });
5736
+ document.addEventListener('mouseout', (e) => {
5737
+ const target = e.target;
5738
+ if (!target || !hoverStarts.has(target)) return;
5739
+ const dwell = Date.now() - hoverStarts.get(target);
5740
+ hoverStarts.delete(target);
5741
+ if (dwell < 100) return; // skip accidental hovers
5742
+ try {
5743
+ const rect = target.getBoundingClientRect();
5744
+ const cx = rect.left + rect.width / 2 + window.scrollX;
5745
+ const cy = rect.top + rect.height / 2 + window.scrollY;
5746
+ const key = getGridCell(cx, cy);
5747
+ hoverGrid.set(key, (hoverGrid.get(key) || 0) + dwell);
5748
+
5749
+ // Element-level dwell: track named/interactive elements hovered ≥ 300ms
5750
+ if (dwell >= 300 && (target.id || ELEMENT_DWELL_TAGS.has(target.tagName))) {
5751
+ const elemKey = `${target.tagName}|${target.id || ''}|${(target.textContent || '').trim().slice(0, 30)}`;
5752
+ const existing = elementDwellsMap.get(elemKey);
5753
+ if (existing) {
5754
+ existing.dwell_ms += dwell;
5755
+ } else {
5756
+ elementDwellsMap.set(elemKey, {
5757
+ tag: target.tagName.toLowerCase(),
5758
+ id: target.id || '',
5759
+ text: (target.textContent || '').trim().slice(0, 50),
5760
+ dwell_ms: dwell
5761
+ });
5762
+ }
5763
+ }
5764
+ } catch (_) {}
5765
+ }, { passive: true });
5766
+
5767
+ // ── Tab visibility → tab_switches + total_tab_hidden_ms
5768
+ document.addEventListener('visibilitychange', () => {
5769
+ if (document.hidden) {
5770
+ tabHiddenTime = Date.now();
5771
+ tabSwitches++;
5772
+ } else if (tabHiddenTime !== null) {
5773
+ totalTabHiddenMs += Date.now() - tabHiddenTime;
5774
+ tabHiddenTime = null;
5775
+ }
5776
+ });
5777
+
5778
+ // ── Scroll reversal detection (direction change ≥ 200 px) + depth recording
5779
+ window.addEventListener('scroll', () => {
5780
+ const curr = window.scrollY;
5781
+ const diff = curr - lastSigScrollY;
5782
+ if (Math.abs(diff) < 50) return;
5783
+ const dir = diff > 0 ? 1 : -1;
5784
+ if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
5785
+ scrollReversals++;
5786
+ scrollReversalDepths.push(getScrollDepth());
5787
+ }
5788
+ if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
5789
+ }, { passive: true });
5790
+
5791
+ // ── Field dwell → per-field focus duration (no content captured)
5792
+ const fieldFocusTimes = new Map();
5793
+ document.addEventListener('focus', (e) => {
5794
+ const t = e.target;
5795
+ if (!t || !['INPUT', 'SELECT', 'TEXTAREA'].includes(t.tagName)) return;
5796
+ if (t.type === 'hidden') return;
5797
+ fieldFocusTimes.set(t, Date.now());
5798
+ }, true);
5799
+ document.addEventListener('blur', (e) => {
5800
+ const t = e.target;
5801
+ if (!t || !fieldFocusTimes.has(t)) return;
5802
+ const dwell = Date.now() - fieldFocusTimes.get(t);
5803
+ fieldFocusTimes.delete(t);
5804
+ if (dwell < 100) return;
5805
+ const label = t.id
5806
+ ? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
5807
+ : (t.placeholder || t.name || t.type || '');
5808
+ fieldDwells.push({
5809
+ field_id: (t.id || t.name || t.type || '').substring(0, 100),
5810
+ field_label: (label || '').substring(0, 100),
5811
+ field_type: t.type || t.tagName.toLowerCase(),
5812
+ dwell_ms: dwell,
5813
+ was_filled: !!(t.value && t.value.length > 0),
5814
+ });
5815
+ }, true);
5816
+
5817
+ // ── Exit intent: mouse leaving viewport toward the top (user about to navigate away)
5818
+ // Also fires as a standalone auto-event for direct querying.
5819
+ let lastExitIntentTime = 0;
5820
+ document.addEventListener('mouseleave', (e) => {
5821
+ if (e.clientY > 0 || e.relatedTarget !== null) return; // only top-edge exits
5822
+ const now = Date.now();
5823
+ if (now - lastExitIntentTime < 2000) return; // debounce: max once per 2s
5824
+ lastExitIntentTime = now;
5825
+ exitIntentCount++;
5826
+ exitIntentLastScrollDepth = getScrollDepth();
5827
+ EventsManager.trackAutoEvent('exit_intent', {
5828
+ scroll_depth: exitIntentLastScrollDepth,
5829
+ time_on_page_ms: now - pageLoadTime,
5830
+ scroll_y: window.scrollY,
5831
+ document_height: Math.min(
5832
+ document.documentElement.scrollHeight || document.body.scrollHeight, 25000
5833
+ )
5834
+ }).catch(() => {});
5835
+ });
5836
+
5837
+ // ── Serialise grid Map to [[col, row, value], ...] sparse array
5838
+ function serializeGrid(grid) {
5839
+ const out = [];
5840
+ grid.forEach((val, key) => {
5841
+ if (val > 0) {
5842
+ const col = Math.floor(key / GRID_SIZE);
5843
+ const row = key % GRID_SIZE;
5844
+ out.push([col, row, Math.round(val)]);
5845
+ }
5846
+ });
5847
+ return out;
5848
+ }
5849
+
5850
+ let summarySent = false;
5851
+ function firePageSummary() {
5852
+ if (summarySent) return; // only fire once per page lifecycle
5853
+ const hasData = moveGrid.size > 0 || hoverGrid.size > 0 || fieldDwells.length > 0
5854
+ || tabSwitches > 0 || scrollReversals > 0 || exitIntentCount > 0
5855
+ || firstInteractionMs !== null || clsScore > 0 || longTasksCount > 0;
5856
+ if (!hasData) return;
5857
+ summarySent = true;
5858
+ try {
5859
+ // Serialize element_dwells: filter ≥ 300ms, sort desc, cap at 20
5860
+ const elementDwells = Array.from(elementDwellsMap.values())
5861
+ .filter(e => e.dwell_ms >= 300)
5862
+ .sort((a, b) => b.dwell_ms - a.dwell_ms)
5863
+ .slice(0, 20);
5864
+
5865
+ EventsManager.trackAutoEvent('page_summary', {
5866
+ scroll_reversals: scrollReversals,
5867
+ scroll_reversal_depths: scrollReversalDepths,
5868
+ tab_switches: tabSwitches,
5869
+ total_tab_hidden_ms: totalTabHiddenMs,
5870
+ move_grid: serializeGrid(moveGrid),
5871
+ hover_grid: serializeGrid(hoverGrid),
5872
+ field_dwells: fieldDwells,
5873
+ element_dwells: elementDwells,
5874
+ first_interaction_ms: firstInteractionMs,
5875
+ exit_intent_count: exitIntentCount,
5876
+ exit_intent_last_scroll_depth: exitIntentLastScrollDepth,
5877
+ cls_score: clsScore,
5878
+ long_tasks_count: longTasksCount,
5879
+ max_long_task_ms: maxLongTaskMs,
5880
+ inp_ms: inpMs,
5881
+ document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
5882
+ document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
5883
+ viewport_width: window.innerWidth,
5884
+ viewport_height: window.innerHeight,
5885
+ });
5886
+ } catch (_) {}
5580
5887
  }
5888
+
5889
+ // Fire on hard navigation / tab close (pagehide is more reliable than beforeunload)
5890
+ window.addEventListener('pagehide', firePageSummary);
5891
+ // Also fire when tab becomes hidden (covers SPA navigation that doesn't fire pagehide)
5892
+ document.addEventListener('visibilitychange', () => {
5893
+ if (document.hidden) firePageSummary();
5894
+ });
5895
+ } catch (error) {}
5581
5896
  },
5582
5897
 
5583
5898
  /**
@@ -5603,6 +5918,7 @@
5603
5918
  this.startAdvancedFormTracking();
5604
5919
  this.startFormAbandonmentTracking();
5605
5920
  this.startElementVisibilityTracking();
5921
+ this.startPageSummaryTracking();
5606
5922
  }
5607
5923
  };
5608
5924
 
@@ -6431,7 +6747,12 @@
6431
6747
  // Performance
6432
6748
  'page_performance': 'performance',
6433
6749
  // Visibility
6434
- 'element_view': 'visibility'
6750
+ 'element_view': 'visibility',
6751
+ // Page summary (aggregate)
6752
+ 'page_summary': 'interaction',
6753
+ // Clipboard & context
6754
+ 'copy_action': 'interaction',
6755
+ 'context_menu': 'interaction',
6435
6756
  };
6436
6757
 
6437
6758
  return categoryMap[eventName] || 'other';
@@ -8228,6 +8549,7 @@
8228
8549
  * The script.js file is inlined during the build process by Rollup.
8229
8550
  */
8230
8551
 
8552
+
8231
8553
  // Create a wrapper that provides programmatic initialization
8232
8554
  const CryptiqueSDK = {
8233
8555
  /**
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "cryptique-sdk",
3
- "version": "1.2.16",
3
+ "version": "1.2.18",
4
4
  "type": "module",
5
5
  "description": "Cryptique Analytics SDK - Comprehensive web analytics and user tracking for modern web applications",
6
6
  "main": "lib/cjs/index.js",
7
+
7
8
  "module": "lib/esm/index.js",
8
9
  "browser": "lib/umd/index.js",
9
10
  "types": "lib/types/index.d.ts",