PageSourceSearch

https://holdfastgame.com/src/js/site-interactions.js

js holdfastgame.com collected 2026-09-30 04:29:33 UTC 4,978 bytes, 138 lines download raw bytes

1(function () {
2    'use strict';
3
4    /**
5     * Fire a GA4 event and log it to the console.
6     * @param {string} eventName  - GA4 event name
7     * @param {Object} params     - Event parameters
8     */
9    function trackEvent(eventName, params) {
10        params = params || {};
11        // console.log('[Tracking] ' + eventName, params);
12        if (typeof gtag === 'function') {
13            gtag('event', eventName, params);
14        }
15    }
16
17    window.trackEvent = trackEvent;
18
19    /**
20     * Build a GA4 ecommerce item object for an edition.
21     * @param {string} editionKey - e.g. 'standard', 'collector'
22     * @param {string} [platform] - e.g. 'direct', 'steam', 'ps', 'xbox'
23     * @returns {Object} GA4 item + currency/value for the event level
24     */
25    function buildEcommerceItem(editionKey, platform) {
26        var editions = (typeof TebexHeadless !== 'undefined') ? TebexHeadless.editions : [];
27        var edition = null;
28        for (var i = 0; i < editions.length; i++) {
29            if (editions[i].key === editionKey) { edition = editions[i]; break; }
30        }
31        if (!edition) return null;
32
33        var item = {
34            item_id: String(edition.packageId),
35            item_name: edition.name,
36            item_category: platform || 'direct',
37            quantity: 1
38        };
39
40        // Only include price/value for direct purchases — external platform
41        // prices are set independently and we don't have access to them
42        var result = { item: item };
43        if (!platform || platform === 'direct') {
44            var priceData = (typeof TebexHeadless !== 'undefined') ? TebexHeadless.prices[editionKey] : null;
45            if (priceData && priceData.price) {
46                var num = parseFloat(priceData.price.replace(/[^0-9.\-]/g, ''));
47                if (!isNaN(num)) item.price = num;
48            }
49            result.currency = (priceData && priceData.currency) ? priceData.currency : 'USD';
50            result.value = item.price || 0;
51        }
52
53        return result;
54    }
55
56    window.buildEcommerceItem = buildEcommerceItem;
57
58    /**
59     * Build GA4 event params from an ecommerceItem result.
60     * Omits currency/value when they aren't present (external platforms).
61     */
62    function ecomEventParams(data) {
63        var params = { items: [data.item] };
64        if ('currency' in data) params.currency = data.currency;
65        if ('value' in data) params.value = data.value;
66        return params;
67    }
68
69    window.ecomEventParams = ecomEventParams;
70
71    /**
72     * Store purchase context in sessionStorage so payment-success.html
73     * can fire the GA4 'purchase' event with item details.
74     */
75    function storePurchaseContext(editionKey, platform) {
76        var data = buildEcommerceItem(editionKey, platform);
77        if (data) {
78            localStorage.setItem('ga4_purchase_context', JSON.stringify(data));
79        }
80    }
81
82    window.storePurchaseContext = storePurchaseContext;
83
84    /* ── view_item: fire once per edition card when it scrolls into view ── */
85    /* Call initViewItemObserver() after Tebex prices have loaded so values are available */
86    var viewItemObserved = {};
87    function initViewItemObserver() {
88        var observer = new IntersectionObserver(function (entries) {
89            entries.forEach(function (entry) {
90                if (!entry.isIntersecting) return;
91                var card = entry.target;
92                var key = card.getAttribute('data-edition-key');
93                if (!key || viewItemObserved[key]) return;
94                viewItemObserved[key] = true;
95
96                var data = buildEcommerceItem(key);
97                if (data) {
98                    trackEvent('view_item', ecomEventParams(data));
99                }
100            });
101        }, { threshold: 0.5 });
102
103        var cards = document.querySelectorAll('.edition-card[data-edition-key]');
104        for (var i = 0; i < cards.length; i++) {
105            observer.observe(cards[i]);
106        }
107    }
108
109    window.initViewItemObserver = initViewItemObserver;
110
111    document.addEventListener('click', function (e) {
112        var target = e.target.closest('[data-track]');
113        if (!target) return;
114
115        var trackType = target.getAttribute('data-track');
116        var location = target.getAttribute('data-track-location') || '';
117
118        switch (trackType) {
119            case 'presskit':
120                trackEvent('click_presskit', { link_location: location });
121                break;
122
123            case 'discord':
124                trackEvent('click_discord', { link_location: location });
125                break;
126
127            case 'creator_programme':
128                trackEvent('click_creator_programme', { link_location: location });
129                break;
130
131            case 'platform':
132                var platform = target.getAttribute('data-track-platform') || '';
133                trackEvent('select_platform', { platform: platform });
134                break;
135
136        }
137    });
138})();

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.