if (typeof lwdgt_executed == 'undefined') { (function (window) { 'use strict'; // Default configuration const DEFAULT_CONFIG = { viewableThreshold: 50, // Percentage of element that must be in view (IAB standard: 50%) viewableDuration: 1000, // Time element must be in view (ms) (IAB standard: 1000ms) throttleInterval: 100, // Throttle check frequency (ms) onViewable: null, // Callback when element becomes viewable removeOnViewable: true, // Call viewable only once observeClass: null, // Class of an element to observe requiredDataAttributes: [], // Attributes will be sent to the API }; class LWViewability { constructor(config = {}) { // Merge provided config with defaults this.config = {...DEFAULT_CONFIG, ...config}; // Store observers and element states this.observed = new Map(); this.viewableTimers = new Map(); // Initialize intersection observer this.observer = new IntersectionObserver( this.#handleIntersections.bind(this), {threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]} ); } start() { document.querySelectorAll('.' + this.config.observeClass).forEach((element) => { this.#observe(element); }); } /** * Start observing a specific element * @param {HTMLElement} element - The element to observe */ #observe(element) { if (!element || !(element instanceof HTMLElement)) { console.error('LWViewability: Invalid element provided for observation'); return; } const isObserved = element.dataset.isObserved; const elementData = {} this.config.requiredDataAttributes.forEach(attr => { elementData[attr] = element.dataset[attr]; }) const missingDataAttributes = this.config.requiredDataAttributes.filter(attr => !element.dataset[attr]); if (missingDataAttributes.length > 0) { console.warn('Element does not have all required data attributes. Missing are: ', missingDataAttributes); return; } if (parseInt(isObserved) === 1) { return; } element.dataset.isObserved = 1; this.observed.set(elementData['elementId'], { config: this.config, isViewable: false, attributesData: elementData, }); // Start observing with IntersectionObserver this.observer.observe(element); return this; } /** * Stop observing a specific element * @param {HTMLElement} element - The element to stop observing * @param {string} elementId - The element to stop observing */ #unobserve(element, elementId) { if (!element) return this; this.observer.unobserve(element); // Clean up any timers and state if (this.viewableTimers.has(elementId)) { clearTimeout(this.viewableTimers.get(elementId)); this.viewableTimers.delete(elementId); } this.observed.delete(elementId); return this; } /** * Handle intersection observer events * @private */ #handleIntersections(entries) { entries.forEach(entry => { const element = entry.target; const elementId = element.dataset.elementId; if (!elementId || !this.observed.has(elementId)) { return; } const elementData = this.observed.get(elementId); const config = elementData.config; const visibleRatio = entry.intersectionRatio; const visiblePercentage = visibleRatio * 100; const wasViewable = elementData.isViewable; const viewableThreshold = config.viewableThreshold; const viewableDuration = config.viewableDuration; // Update visibility state elementData.isViewable = visiblePercentage >= viewableThreshold; // Handle viewability changes and timing if (elementData.isViewable && !wasViewable) { // Start timer for viewable duration if (this.viewableTimers.has(elementId)) { clearTimeout(this.viewableTimers.get(elementId)); } this.viewableTimers.set(elementId, setTimeout(() => { if (elementData.isViewable) { if (typeof config.onViewable === 'function') { config.onViewable(elementData.attributesData); if (config.removeOnViewable) { this.#unobserve(element, elementId); } } } this.viewableTimers.delete(elementId); }, viewableDuration)); } else if (!elementData.isViewable && wasViewable) { // Clear any pending viewability timer if (this.viewableTimers.has(elementId)) { clearTimeout(this.viewableTimers.get(elementId)); this.viewableTimers.delete(elementId); } } }); } /** * Disconnect all observers and clean up */ #destroy() { this.observer.disconnect(); // Clear all timers for (const timerId of this.viewableTimers.keys()) { clearTimeout(this.viewableTimers.get(timerId)); } this.viewableTimers.clear(); this.observed.clear(); return this; } } // Expose to window window.LWViewability = LWViewability; })(window); (function() { window.handleWTGAd = function (e) { if (e.detail) { let slot_info = window.linker_wtg_slots[e.detail.slot]; var l_wtgBox = document.querySelector('.wid-' + slot_info['widget'] + ' .linker-widget-col.lwc-' + slot_info['position']); let l_BoxURL = l_wtgBox.getElementsByTagName('a')[0]; l_BoxURL.setAttribute('href', e.detail.url); let l_BoxImg = l_wtgBox.getElementsByTagName('img')[0]; l_BoxImg.setAttribute('src', e.detail.image); let l_BoxText = l_wtgBox.getElementsByTagName('span')[0]; l_BoxText.innerText = e.detail.title; //append impressionTrackers e.detail.impressionTrackers?.forEach(tracker => { let imgTracker = document.createElement('img'); imgTracker.setAttribute('src', tracker); imgTracker.style.display = "none"; l_wtgBox.appendChild(imgTracker); }); // if clickTrackers present add them to click event if (e.detail.clickTrackers) { l_BoxURL.addEventListener('click', () => { e.detail.clickTrackers.forEach(tracker => { let clcTracker = document.createElement('img'); clcTracker.setAttribute('src', tracker); clcTracker.style.display = "none"; l_wtgBox.appendChild(clcTracker); }) }); } } } window.registerWTGListeners = function () { document.addEventListener('wtgAdLoaded', window.handleWTGAd); } const lwRegisterViewable = (data) => { navigator.sendBeacon('https://d.linker.bg/viewable', new URLSearchParams(data)); }; if(document.readyState === 'interactive' || document.readyState === 'complete') { var widgetObserver = new LWViewability({ onViewable: lwRegisterViewable, viewableThreshold: 2, viewableDuration: 0, observeClass: 'lw-observability-widget', requiredDataAttributes: ['impressionId', 'elementId', 'elementType', 'wid', 'lid'], }); var linkObserver = new LWViewability({ onViewable: lwRegisterViewable, viewableThreshold: 50, viewableDuration: 1000, observeClass: 'lw-observability-link', requiredDataAttributes: ['impressionId', 'elementId', 'elementType', 'wid', 'lid'], }); window.registerWTGListeners(); let tcf_compliant = (typeof __tcfapi !== 'undefined'); window.linker_gdpr_consent = ''; window.linker_gdpr_applies = 0; var lwdgt_link = document.createElement('link'); lwdgt_link.rel = 'stylesheet'; lwdgt_link.type = 'text/css'; lwdgt_link.href = 'https://d.linker.bg/widget/lw.css'; document.getElementsByTagName('head')[0].appendChild(lwdgt_link); window.lwdgt_process = function(url, widget_id) { if(url === null || url === ''){ let wid_element = document.getElementById(widget_id); let document_url = btoa(document.URL); let url_string = window.location.href; let destination_url = new URL(url_string); let linker_preview = destination_url.searchParams.get("linker_preview"); url = "https://d.linker.bg/widget/lw?&wid=" + wid_element.getAttribute('data-wid') + "&=" + wid_element.getAttribute('data-amp') + "&surl=" + document_url + "&gdpr_consent=" + window.linker_gdpr_consent + "&gdpr_applies=" + window.linker_gdpr_applies; if (linker_preview) { url += "&linker_preview=" + linker_preview; } } var lwdgt_request = new XMLHttpRequest(); lwdgt_request.withCredentials = true; lwdgt_request.onreadystatechange = function() { if (lwdgt_request.readyState == 4 && lwdgt_request.status == 200) { try { document.getElementById(widget_id).innerHTML = JSON.parse(lwdgt_request.responseText); var arr = document.getElementById(widget_id).getElementsByTagName('script'); for (var n = 0; n < arr.length; n++) { var s = document.createElement('script'); s.setAttribute('type', 'text/javascript'); if(arr[n].src){ s.setAttribute('src', arr[n].src); } if(arr[n].innerHTML){ s.innerHTML = arr[n].innerHTML; } s.async = true; document.body.appendChild(s); } widgetObserver.start(); linkObserver.start(); } catch (error) { } } }; lwdgt_request.open('GET', url, true); lwdgt_request.send(); } window.linkerReloadElements = function (onlyEmpty = false) { let lwdgt_divs = document.getElementsByClassName('lwdgt'); let document_url = btoa(document.URL); let url_string = window.location.href; let destination_url = new URL(url_string); let linker_preview = destination_url.searchParams.get("linker_preview"); for (var lw_i = 0; lw_i < lwdgt_divs.length; lw_i++) { if(onlyEmpty && lwdgt_divs[lw_i].innerHTML.trim() !== ''){ continue; } lwdgt_divs[lw_i].id = 'lwdgt-' + Math.floor((Math.random() * 10000) + 1); let widget_url = "https://d.linker.bg/widget/lw?&wid=" + lwdgt_divs[lw_i].getAttribute('data-wid') + "&=" + lwdgt_divs[lw_i].getAttribute('data-amp') + "&surl=" + document_url + "&gdpr_consent=" + window.linker_gdpr_consent + "&gdpr_applies=" + window.linker_gdpr_applies; if (linker_preview) { widget_url += "&linker_preview=" + linker_preview; } lwdgt_process(widget_url, lwdgt_divs[lw_i].id); } } window.reloadLinkerElements = function () { if (typeof window.lwdgt_timestamp === 'undefined') { window.lwdgt_timestamp = Date.now(); } else { if (Date.now() - window.lwdgt_timestamp < 1000) { return null; } window.lwdgt_timestamp = Date.now(); } linkerReloadElements(true); } const linker_consent_callback = (tcData, success) => { if (success && (tcData.eventStatus == "tcloaded" || tcData.eventStatus == "useractioncomplete")) { window.linker_gdpr_consent = tcData.tcString; window.linker_gdpr_applies = tcData.gdprApplies ? 1 : 0; linkerReloadElements(true); __tcfapi('removeEventListener', 2, (success) => { if (success) { } }, linker_consent_callback); } else { // nothing atm } } if(!tcf_compliant) { linkerReloadElements(false); } else { __tcfapi('addEventListener', 2, linker_consent_callback); } } else setTimeout(arguments.callee, 200); })(); var lwdgt_executed = true; }