PageSourceSearch

https://samuraiswordtokyo.com/wp-content/plugins/samurai-ga4-tracking/assets/samurai-ga4.js?ver=0.3.3

js samuraiswordtokyo.com collected 2026-10-06 04:17:21 UTC 25,884 bytes, 732 lines download raw bytes

1(function () {
2  'use strict';
3
4  window.dataLayer = window.dataLayer || [];
5
6  const config = window.SAMURAI_GA4_CONFIG || {};
7  const ecommerceEvents = new Set([
8    'view_item',
9    'view_item_list',
10    'select_item',
11    'add_to_cart',
12    'remove_from_cart',
13    'view_cart',
14    'begin_checkout',
15    'checkout_button_click',
16    'add_shipping_info',
17    'add_payment_info',
18    'payment_button_click',
19    'checkout_error',
20    'purchase'
21  ]);
22
23  const qoCartReported = new Map();
24  const qoCartPending = new Map();
25  const qoCartTimers = new Map();
26  const variationPrices = new Map();
27  let lastStandardAddIntent = null;
28  let lastRemoveIntent = null;
29  let checkoutErrorSentAt = 0;
30
31  function storageKey(key) {
32    return 'samurai_ga4_once:' + String(key || '');
33  }
34
35  function canUseSessionStorage() {
36    try {
37      window.sessionStorage.setItem('__samurai_ga4_test', '1');
38      window.sessionStorage.removeItem('__samurai_ga4_test');
39      return true;
40    } catch (_) {
41      return false;
42    }
43  }
44
45  const hasSessionStorage = canUseSessionStorage();
46
47  // ---------------------------------------------------------------------
48  // Durable one-shot store (purchase dedup).
49  //
50  // sessionStorage is per-tab and dies with the tab, so the same
51  // transaction_id was re-sent to GA4 whenever the order-received page was
52  // opened in a second tab or revisited later from My Account. localStorage
53  // is shared by every tab of the origin and survives the browser closing,
54  // so a transaction_id recorded here is never sent twice from this browser.
55  // Only 'purchase:<transaction_id>' keys use it; every other event keeps
56  // the existing per-session behaviour.
57  // ---------------------------------------------------------------------
58  const DURABLE_STORE_KEY = 'samurai_ga4_sent_once';
59  const DURABLE_TTL_MS = 400 * 24 * 60 * 60 * 1000;
60  const DURABLE_MAX_ENTRIES = 60;
61
62  function canUseLocalStorage() {
63    try {
64      window.localStorage.setItem('__samurai_ga4_ls_test', '1');
65      window.localStorage.removeItem('__samurai_ga4_ls_test');
66      return true;
67    } catch (_) {
68      return false;
69    }
70  }
71
72  const hasLocalStorage = canUseLocalStorage();
73
74  function isDurableKey(key) {
75    return String(key || '').indexOf('purchase:') === 0;
76  }
77
78  function readDurableEntries() {
79    try {
80      const parsed = JSON.parse(window.localStorage.getItem(DURABLE_STORE_KEY) || '[]');
81      return Array.isArray(parsed) ? parsed : [];
82    } catch (_) {
83      return [];
84    }
85  }
86
87  function durableHas(key) {
88    const now = Date.now();
89    return readDurableEntries().some(function (entry) {
90      return entry && entry.k === key && (now - Number(entry.t || 0)) < DURABLE_TTL_MS;
91    });
92  }
93
94  function durableRemember(key) {
95    const now = Date.now();
96    const kept = readDurableEntries().filter(function (entry) {
97      return entry && entry.k && entry.k !== key && (now - Number(entry.t || 0)) < DURABLE_TTL_MS;
98    });
99    kept.push({ k: key, t: now });
100
101    try {
102      window.localStorage.setItem(DURABLE_STORE_KEY, JSON.stringify(kept.slice(-DURABLE_MAX_ENTRIES)));
103    } catch (_) {}
104  }
105
106  function push(payload) {
107    if (!payload || typeof payload !== 'object' || !payload.event) return;
108    if (config.enabled === false && !config.debug) return;
109
110    if (payload.ecommerce && ecommerceEvents.has(payload.event)) {
111      window.dataLayer.push({ ecommerce: null });
112    }
113
114    window.dataLayer.push(payload);
115
116    if (config.debug && window.console && typeof window.console.info === 'function') {
117      window.console.info('[Samurai GA4]', payload.event, payload);
118    }
119  }
120
121  function pushOnce(key, payload) {
122    if (!key) {
123      push(payload);
124      return;
125    }
126
127    if (hasLocalStorage && isDurableKey(key)) {
128      if (durableHas(key)) return;
129      push(payload);
130      durableRemember(key);
131      return;
132    }
133
134    const keyName = storageKey(key);
135    if (hasSessionStorage && window.sessionStorage.getItem(keyName) === '1') return;
136
137    push(payload);
138
139    if (hasSessionStorage) {
140      window.sessionStorage.setItem(keyName, '1');
141    }
142  }
143
144  function cleanText(value, maxLength) {
145    const text = String(value || '').replace(/\s+/g, ' ').trim();
146    return maxLength ? text.slice(0, maxLength) : text;
147  }
148
149  function cleanDimension(value) {
150    return cleanText(value, 100);
151  }
152
153  function parseMoney(value) {
154    if (typeof value === 'number') return Math.round(value * 100) / 100;
155    const normalized = String(value || '').replace(/[^\d.-]/g, '');
156    const parsed = parseFloat(normalized);
157    return Number.isFinite(parsed) ? Math.round(parsed * 100) / 100 : 0;
158  }
159
160  function parseQty(value) {
161    const parsed = parseInt(String(value || ''), 10);
162    return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
163  }
164
165  function clonePlain(value) {
166    if (!value || typeof value !== 'object') return {};
167    try {
168      return JSON.parse(JSON.stringify(value));
169    } catch (_) {
170      return {};
171    }
172  }
173
174  function ecommerceFromConfig(extra) {
175    const ecommerce = clonePlain(config.ecommerce);
176    if (!ecommerce.currency) ecommerce.currency = config.currency || 'JPY';
177    if (!Array.isArray(ecommerce.items)) ecommerce.items = [];
178    return Object.assign(ecommerce, extra || {});
179  }
180
181  function ecommerceForItem(item, quantity) {
182    const qty = Math.max(1, parseQty(quantity || item.quantity || 1));
183    const price = parseMoney(item.price);
184    return {
185      currency: config.currency || 'JPY',
186      value: price > 0 ? Math.round(price * qty * 100) / 100 : undefined,
187      items: [Object.assign({}, item, { quantity: qty })]
188    };
189  }
190
191  function removeEmptyValues(value) {
192    Object.keys(value).forEach((key) => {
193      if (value[key] === '' || value[key] === null || typeof value[key] === 'undefined') {
194        delete value[key];
195      }
196    });
197    return value;
198  }
199
200  function cssEscape(value) {
201    if (window.CSS && typeof window.CSS.escape === 'function') {
202      return window.CSS.escape(String(value));
203    }
204
205    return String(value).replace(/["\\]/g, '\\$&');
206  }
207
208  function qoNumericId(input, row) {
209    const variation = parseInt(input?.dataset.variationId || '', 10);
210    if (Number.isFinite(variation) && variation > 0) return String(variation);
211    const product = parseInt(input?.dataset.productId || '', 10);
212    if (Number.isFinite(product) && product > 0) return String(product);
213    return String(input?.dataset.variantId || row?.dataset.productId || '');
214  }
215
216  function productFromQuickOrderRow(target, quantity) {
217    const row = target && target.closest ? target.closest('tr[data-product-id], tr') : null;
218    if (!row) return {};
219
220    const title = row.querySelector('.sst-qo__pname, [data-product-title], .sst-qo__title, .sst-qo__prod');
221    const input = target.matches && target.matches('input[data-variant-id]')
222      ? target
223      : (target.closest && target.closest('.sst-qo__offer-cell')?.querySelector('input[data-variant-id]'))
224        || row.querySelector('input[data-variant-id]');
225    const activeOffer = target.closest ? target.closest('.sst-qo__offer-cell') : null;
226    const offerLabel = activeOffer ? cleanText(activeOffer.querySelector('.sst-qo__offer-label')?.textContent) : cleanText(input?.dataset.offer || '');
227    const group = row.closest('.sst-qo__group');
228    const listName = group?.dataset.group ? 'Quick Order - ' + cleanDimension(group.dataset.group) : 'Quick Order';
229    const item = {
230      item_id: qoNumericId(input, row),
231      item_sku: cleanDimension(input?.dataset.sku || ''),
232      item_name: cleanText(input?.dataset.name || row.dataset.pname || title?.textContent || '', 100),
233      item_variant: cleanDimension(offerLabel || input?.dataset.offer || ''),
234      item_category: cleanDimension(row.dataset.group || group?.dataset.group || ''),
235      item_list_name: listName,
236      price: parseMoney(input?.dataset.price || row.dataset.price || 0)
237    };
238
239    if (quantity) item.quantity = Math.max(1, parseQty(quantity));
240    return removeEmptyValues(item);
241  }
242
243  function productFromElement(target, quantity) {
244    if (!target || !target.closest) return {};
245    if (target.closest('.sst-qo')) return productFromQuickOrderRow(target, quantity);
246
247    const root = target.closest('form.cart, li.product, .product, tr.cart_item, .cart_item') || target;
248
249    const baked = root.querySelector ? root.querySelector('.sst-ga4-item-data[data-ga4-item]') : null;
250    if (baked) {
251      let item = {};
252      try {
253        item = JSON.parse(baked.dataset.ga4Item || '{}') || {};
254      } catch (_) {
255        item = {};
256      }
257
258      const variationInput = root.querySelector('input[name="variation_id"]');
259      const variationId = parseInt(variationInput?.value || '', 10);
260      if (Number.isFinite(variationId) && variationId > 0) {
261        item.item_id = String(variationId);
262        const variationPrice = variationPrices.get(String(variationId));
263        if (typeof variationPrice === 'number') item.price = variationPrice;
264        item.item_variant = cleanDimension(root.querySelector('[name^="attribute_"] option:checked')?.textContent || item.item_variant || '');
265      }
266
267      const bakedQty = root.querySelector('input.qty, input[name="quantity"]');
268      item.item_name = cleanText(item.item_name || '', 100);
269      item.price = parseMoney(item.price);
270      item.quantity = Math.max(1, parseQty(quantity || bakedQty?.value || 1));
271      return removeEmptyValues(item);
272    }
273
274    const qtyInput = root.querySelector('input.qty, input[name="quantity"]');
275    const name = root.querySelector('.product_title, .woocommerce-loop-product__title, .product-name a, .product-name, .cart_item .product-name');
276    const price = root.querySelector('.price .amount, .woocommerce-Price-amount, .product-price .amount');
277    const variation = root.querySelector('input[name="variation_id"]');
278    const product = root.querySelector('input[name="product_id"], button[name="add-to-cart"], input[name="add-to-cart"]');
279    const item = {
280      item_id: target.dataset.product_id || target.dataset.productId || variation?.value || product?.value || target.value || '',
281      item_name: cleanText(target.dataset.product_name || target.dataset.productName || name?.textContent || target.getAttribute('aria-label') || '', 100),
282      item_variant: cleanDimension(root.querySelector('[name^="attribute_"] option:checked')?.textContent || ''),
283      price: parseMoney(target.dataset.price || price?.textContent || 0),
284      quantity: Math.max(1, parseQty(quantity || qtyInput?.value || target.dataset.quantity || 1))
285    };
286
287    return removeEmptyValues(item);
288  }
289
290  function pushItemEvent(eventName, source, item, quantity) {
291    if (!item || (!item.item_id && !item.item_name)) return;
292
293    const payload = {
294      event: eventName,
295      source: source,
296      ecommerce: ecommerceForItem(item, quantity)
297    };
298
299    if (!payload.ecommerce.value) delete payload.ecommerce.value;
300    push(payload);
301  }
302
303  function hasItemIdentity(item) {
304    return Boolean(item && (item.item_id || item.item_name));
305  }
306
307  // ----- Quick Order cart tracking -----
308  // quick-order.js dispatches CustomEvents from its single staging mutation
309  // point (setQuickOrderCartQuantity) and its server-snapshot restore path
310  // (applyQoCartSnapshot). Deltas are computed here against the last reported
311  // quantity per variant, so programmatic restores never emit cart events.
312
313  function qoItemFromCartDetail(detail) {
314    const variation = parseInt(detail.variationId, 10);
315    const product = parseInt(detail.productId, 10);
316    const item = {
317      item_id: Number.isFinite(variation) && variation > 0
318        ? String(variation)
319        : (Number.isFinite(product) && product > 0 ? String(product) : String(detail.vid || '')),
320      item_sku: cleanDimension(detail.sku || ''),
321      item_name: cleanText(detail.name || '', 100),
322      item_list_name: 'Quick Order',
323      price: parseMoney(detail.price)
324    };
325
326    return removeEmptyValues(item);
327  }
328
329  function flushQoCartDelta(vid) {
330    qoCartTimers.delete(vid);
331    const pending = qoCartPending.get(vid);
332    qoCartPending.delete(vid);
333    if (!pending) return;
334
335    const previous = qoCartReported.has(vid) ? qoCartReported.get(vid) : 0;
336    const delta = pending.quantity - previous;
337    qoCartReported.set(vid, pending.quantity);
338    if (delta === 0) return;
339
340    const eventName = delta > 0 ? 'add_to_cart' : 'remove_from_cart';
341    pushItemEvent(eventName, 'quick_order', qoItemFromCartDetail(pending), Math.abs(delta));
342  }
343
344  function onQoCartQty(event) {
345    const detail = event.detail || {};
346    const vid = String(detail.vid || '');
347    if (!vid) return;
348
349    const quantity = Math.max(0, parseInt(detail.quantity, 10) || 0);
350
351    if (detail.silent) {
352      window.clearTimeout(qoCartTimers.get(vid));
353      qoCartTimers.delete(vid);
354      qoCartPending.delete(vid);
355      qoCartReported.set(vid, quantity);
356      return;
357    }
358
359    qoCartPending.set(vid, Object.assign({}, detail, { quantity: quantity }));
360    window.clearTimeout(qoCartTimers.get(vid));
361    qoCartTimers.set(vid, window.setTimeout(function () {
362      flushQoCartDelta(vid);
363    }, 900));
364  }
365
366  function onQoCartBaseline(event) {
367    const items = (event.detail && event.detail.items) || [];
368    const seen = new Set();
369
370    items.forEach(function (entry) {
371      const vid = String(entry.vid || '');
372      if (!vid) return;
373      seen.add(vid);
374      if (qoCartTimers.has(vid)) return;
375      qoCartReported.set(vid, Math.max(0, parseInt(entry.quantity, 10) || 0));
376    });
377
378    qoCartReported.forEach(function (_qty, vid) {
379      if (!seen.has(vid) && !qoCartTimers.has(vid)) qoCartReported.set(vid, 0);
380    });
381  }
382
383  function onQoViewItem(event) {
384    const row = event.detail && event.detail.row;
385    if (!row || !row.querySelector) return;
386
387    const item = productFromQuickOrderRow(row, 1);
388    if (!hasItemIdentity(item)) return;
389
390    pushOnce('view_item:qo:' + (item.item_id || item.item_name), {
391      event: 'view_item',
392      source: 'quick_order',
393      ecommerce: ecommerceForItem(item, 1)
394    });
395  }
396
397  function qoListHash(ids) {
398    const text = ids.join('|');
399    let hash = 5381;
400    for (let i = 0; i < text.length; i += 1) {
401      hash = ((hash << 5) + hash + text.charCodeAt(i)) >>> 0;
402    }
403    return hash.toString(36);
404  }
405
406  function pushQuickOrderGroupList(group) {
407    if (!group || !group.querySelectorAll) return;
408
409    const items = [];
410    Array.from(group.querySelectorAll('tbody tr')).slice(0, 25).forEach(function (row, index) {
411      const item = productFromQuickOrderRow(row, 1);
412      if (!hasItemIdentity(item)) return;
413      item.index = index;
414      items.push(item);
415    });
416    if (!items.length) return;
417
418    const groupKey = cleanDimension(group.dataset.group || 'quick_order');
419    pushOnce('view_item_list:qo:' + groupKey + ':' + qoListHash(items.map(function (item) { return item.item_id || ''; })), {
420      event: 'view_item_list',
421      source: 'quick_order',
422      ecommerce: {
423        item_list_id: 'quick_order:' + groupKey,
424        item_list_name: 'Quick Order - ' + groupKey,
425        items: items
426      }
427    });
428  }
429
430  function pushQuickOrderLists() {
431    document.querySelectorAll('.sst-qo__group').forEach(pushQuickOrderGroupList);
432  }
433
434  function onQoRowsRendered(event) {
435    const group = event.detail && event.detail.group;
436    if (group) pushQuickOrderGroupList(group);
437  }
438
439  function pushQuickOrderView() {
440    const root = document.querySelector('.sst-qo');
441    if (!root) return;
442
443    push({
444      event: 'quick_order_view',
445      source: 'quick_order'
446    });
447  }
448
449  function selectedShippingTier() {
450    const checked = document.querySelector('input[name^="shipping_method"]:checked');
451    const hidden = document.querySelector('input[name^="shipping_method"][type="hidden"]');
452    const input = checked || hidden;
453    if (!input) return '';
454
455    const label = input.id ? document.querySelector('label[for="' + cssEscape(input.id) + '"]') : null;
456    return cleanDimension(label?.textContent || input.value || '');
457  }
458
459  function selectedPaymentType() {
460    const checked = document.querySelector('input[name="payment_method"]:checked');
461    if (!checked) return '';
462
463    const label = checked.id ? document.querySelector('label[for="' + cssEscape(checked.id) + '"]') : null;
464    return cleanDimension(label?.textContent || checked.value || '');
465  }
466
467  function fieldValue(id) {
468    const field = document.getElementById(id);
469    if (!field || field.disabled) return '';
470    return cleanText(field.value);
471  }
472
473  function requiredFieldComplete(id) {
474    const field = document.getElementById(id);
475    if (!field || field.disabled) return true;
476
477    const wrapper = field.closest('.form-row, p, label, div');
478    if (wrapper && wrapper.classList.contains('woocommerce-invalid')) return false;
479    if (wrapper && wrapper.classList.contains('validate-required')) return fieldValue(id) !== '';
480    if (field.hasAttribute('required') || field.getAttribute('aria-required') === 'true') return fieldValue(id) !== '';
481
482    return true;
483  }
484
485  function checkoutAddressComplete(prefix) {
486    const fields = [prefix + '_first_name', prefix + '_last_name', prefix + '_country', prefix + '_address_1', prefix + '_city'];
487    return fields.every(requiredFieldComplete);
488  }
489
490  function checkoutContactComplete() {
491    return requiredFieldComplete('billing_email') && requiredFieldComplete('billing_phone');
492  }
493
494  function checkoutShippingInfoComplete() {
495    const hasCheckout = document.querySelector('form.checkout, form.woocommerce-checkout');
496    if (!hasCheckout) return false;
497
498    const billingComplete = checkoutAddressComplete('billing') && checkoutContactComplete();
499    const shipDifferent = document.getElementById('ship-to-different-address-checkbox');
500    const shippingComplete = shipDifferent && shipDifferent.checked ? checkoutAddressComplete('shipping') : true;
501
502    return billingComplete && shippingComplete;
503  }
504
505  function maybePushShippingInfo() {
506    if (!checkoutShippingInfoComplete()) return;
507
508    const shippingTier = selectedShippingTier();
509    const ecommerce = ecommerceFromConfig(shippingTier ? { shipping_tier: shippingTier } : {});
510    const key = 'add_shipping_info:' + (config.cart_hash || window.location.pathname);
511
512    pushOnce(key, {
513      event: 'add_shipping_info',
514      source: 'checkout',
515      checkout_step: 'shipping_info',
516      shipping_tier: shippingTier,
517      ecommerce: ecommerce
518    });
519  }
520
521  function maybePushPaymentInfo() {
522    const paymentType = selectedPaymentType();
523    if (!paymentType) return;
524
525    const ecommerce = ecommerceFromConfig({ payment_type: paymentType });
526    const key = 'add_payment_info:' + (config.cart_hash || window.location.pathname);
527
528    pushOnce(key, {
529      event: 'add_payment_info',
530      source: 'checkout',
531      checkout_step: 'payment_info',
532      payment_type: paymentType,
533      ecommerce: ecommerce
534    });
535  }
536
537  function pushPaymentButtonClick(target) {
538    const paymentType = selectedPaymentType();
539    const buttonId = target?.id || target?.name || '';
540    maybePushShippingInfo();
541    maybePushPaymentInfo();
542
543    window.setTimeout(function () {
544      push({
545        event: 'payment_button_click',
546        source: 'checkout',
547        checkout_step: 'place_order',
548        payment_type: paymentType,
549        button_id: buttonId,
550        ecommerce: ecommerceFromConfig(paymentType ? { payment_type: paymentType } : {})
551      });
552    }, 250);
553  }
554
555  function checkoutErrorType() {
556    const errorText = cleanText(Array.from(document.querySelectorAll('.woocommerce-error, .wc-block-components-notice-banner.is-error'))
557      .map((node) => node.textContent)
558      .join(' '))
559      .toLowerCase();
560
561    if (/payment|card|wise|paypal|stripe|支払|決済/.test(errorText)) return 'payment_failed';
562    if (/required|invalid|valid|missing|必須|入力|無効/.test(errorText)) return 'validation_error';
563    if (/stock|在庫/.test(errorText)) return 'stock_error';
564
565    return 'checkout_error';
566  }
567
568  function pushCheckoutError() {
569    const now = Date.now();
570    if (now - checkoutErrorSentAt < 1500) return;
571    checkoutErrorSentAt = now;
572
573    const errors = document.querySelectorAll('.woocommerce-error li, .woocommerce-error, .wc-block-components-notice-banner.is-error');
574    push({
575      event: 'checkout_error',
576      source: 'checkout',
577      checkout_step: 'place_order',
578      error_type: checkoutErrorType(),
579      error_count: Math.max(1, errors.length),
580      ecommerce: ecommerceFromConfig()
581    });
582  }
583
584  function onDocumentClick(event) {
585    const target = event.target;
586    if (!target || !target.closest) return;
587
588    const qoProductLink = target.closest('.sst-qo__group tbody tr a.sst-qo__prod');
589    if (qoProductLink) {
590      const item = productFromQuickOrderRow(qoProductLink, 1);
591      if (hasItemIdentity(item)) {
592        push({
593          event: 'select_item',
594          source: 'quick_order',
595          ecommerce: {
596            currency: config.currency || 'JPY',
597            item_list_id: 'quick_order',
598            item_list_name: item.item_list_name || 'Quick Order',
599            items: [item]
600          }
601        });
602      }
603      return;
604    }
605
606    const qoCheckout = target.closest('.sst-qo [data-action="checkout"]');
607    if (qoCheckout) {
608      push({
609        event: 'checkout_button_click',
610        source: 'quick_order',
611        button_id: qoCheckout.dataset.action || 'checkout',
612        ecommerce: ecommerceFromConfig()
613      });
614      return;
615    }
616
617    const paymentButton = target.closest('#place_order, button[name="woocommerce_checkout_place_order"], .wc-block-components-checkout-place-order-button, .wc-block-components-express-payment__button');
618    if (paymentButton) {
619      pushPaymentButtonClick(paymentButton);
620      return;
621    }
622
623    const addButton = target.closest('.single_add_to_cart_button, .add_to_cart_button, button[name="add-to-cart"]');
624    if (addButton && !addButton.disabled && !addButton.closest('.sst-qo')) {
625      const item = productFromElement(addButton);
626      lastStandardAddIntent = item;
627      if (!addButton.classList.contains('ajax_add_to_cart')) {
628        pushItemEvent('add_to_cart', 'woocommerce', item, item.quantity || 1);
629      }
630      return;
631    }
632
633    const removeButton = target.closest('.remove_from_cart_button, a.remove');
634    if (removeButton && !removeButton.closest('.sst-qo')) {
635      const item = productFromElement(removeButton);
636      lastRemoveIntent = item;
637      if (!removeButton.classList.contains('remove_from_cart_button')) {
638        pushItemEvent('remove_from_cart', 'woocommerce', item, item.quantity || 1);
639      }
640    }
641  }
642
643  function bindWooCommerceEvents() {
644    if (!window.jQuery) return;
645
646    window.jQuery(document.body).on('added_to_cart', function (_event, _fragments, _cartHash, button) {
647      const el = button && button.length ? button[0] : null;
648      const item = productFromElement(el);
649      const trackedItem = hasItemIdentity(item) ? item : lastStandardAddIntent;
650      pushItemEvent('add_to_cart', 'woocommerce', trackedItem, trackedItem?.quantity || 1);
651    });
652
653    window.jQuery(document.body).on('removed_from_cart', function (_event, _fragments, _cartHash, button) {
654      const el = button && button.length ? button[0] : null;
655      const item = productFromElement(el);
656      const trackedItem = hasItemIdentity(item) ? item : lastRemoveIntent;
657      pushItemEvent('remove_from_cart', 'woocommerce', trackedItem, trackedItem?.quantity || 1);
658    });
659
660    window.jQuery(document.body).on('updated_checkout', function () {
661      window.setTimeout(maybePushShippingInfo, 250);
662    });
663
664    window.jQuery(document.body).on('found_variation', 'form.variations_form', function (_event, variation) {
665      if (!variation || !variation.variation_id) return;
666      if (typeof variation.display_price === 'number') {
667        variationPrices.set(String(variation.variation_id), Math.round(variation.display_price * 100) / 100);
668      }
669    });
670
671    window.jQuery(document.body).on('checkout_error', function () {
672      window.setTimeout(pushCheckoutError, 100);
673    });
674  }
675
676  function bindCheckoutEvents() {
677    const checkoutForm = document.querySelector('form.checkout, form.woocommerce-checkout');
678    if (!checkoutForm) return;
679
680    let checkoutTimer = 0;
681    const schedule = function () {
682      window.clearTimeout(checkoutTimer);
683      checkoutTimer = window.setTimeout(maybePushShippingInfo, 350);
684    };
685
686    checkoutForm.addEventListener('change', schedule, true);
687    checkoutForm.addEventListener('blur', schedule, true);
688    schedule();
689
690    const observer = new MutationObserver(function () {
691      if (document.querySelector('.woocommerce-error, .wc-block-components-notice-banner.is-error')) {
692        pushCheckoutError();
693      }
694    });
695
696    observer.observe(document.body, { childList: true, subtree: true });
697  }
698
699  function onReady(callback) {
700    if (document.readyState === 'loading') {
701      document.addEventListener('DOMContentLoaded', callback, { once: true });
702      return;
703    }
704
705    callback();
706  }
707
708  window.SamuraiGA4 = {
709    push: push,
710    pushOnce: pushOnce
711  };
712
713  document.addEventListener('click', onDocumentClick, true);
714  document.addEventListener('sst:qo:cart-qty', onQoCartQty);
715  document.addEventListener('sst:qo:cart-baseline', onQoCartBaseline);
716  document.addEventListener('sst:qo:view-item', onQoViewItem);
717  document.addEventListener('sst:qo:rows-rendered', onQoRowsRendered);
718
719  onReady(function () {
720    bindWooCommerceEvents();
721    bindCheckoutEvents();
722    pushQuickOrderLists();
723    window.setTimeout(pushQuickOrderView, 1200);
724
725    try {
726      const position = new URLSearchParams(window.location.search).get('sst_pos');
727      if (position) {
728        push({ event: 'internal_nav', source: 'internal', position: cleanDimension(position) });
729      }
730    } catch (_) {}
731  });
732})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.