1// Custom Popup Implementation - No Magnific Popup Required 2(function ($) { 3 // Popup settings arrive via data-* attributes, which the browser has already 4 // decoded â so the server's escaping is gone by the time these values are 5 // spliced back into markup below. Escape again at the sink. 6 function escapeAttr(value) { 7 return String(value == null ? "" : value) 8 .replace(/&/g, "&") 9 .replace(/"/g, """) 10 .replace(/</g, "<") 11 .replace(/>/g, ">"); 12 } 13 14 // These two land in a class attribute, where only class-name characters are 15 // ever legitimate; anything else is dropped rather than escaped. 16 function sanitizeClassToken(value) { 17 return String(value == null ? "" : value).replace(/[^\w\s-]/g, ""); 18 } 19 20 // Custom popup implementation object 21 const DnxteCustomPopup = { 22 // Current active popup (for backward compatibility) 23 currentPopup: null, 24 // Track all active popups 25 activePopups: [], 26 27 // Cache of detached popup templates keyed by their source selector. 28 templates: {}, 29 30 // Initialize the popup 31 open: function (config) { 32 // The popup markup is server-rendered once inside the hidden #dnxte_popup 33 // holder and cloned into the visible wrapper on open. Cloning the LIVE 34 // holder each time left two elements in the DOM sharing the same ids (the 35 // popup container id plus any custom module ids the user set, e.g. 36 // #popupcoutdown on a countdown) â invalid HTML that makes 37 // getElementById()/#id lookups resolve to the hidden copy. On first use, 38 // cache a detached template (ids intact, captured after Divi has 39 // initialized the holder so clone behavior is unchanged) and remove the 40 // live-holder copy, so only the visible clone carries the ids. 41 const src = config.items.src; 42 let popupContent; 43 if (typeof src === "string") { 44 if (!this.templates[src]) { 45 const liveSource = $(src); 46 if (!liveSource.length) return; 47 this.templates[src] = liveSource.clone(); 48 liveSource.remove(); 49 } 50 popupContent = this.templates[src]; 51 } else { 52 // Non-string src (already a jQuery/DOM node): use as-is, no dedup needed. 53 popupContent = $(src); 54 } 55 if (!popupContent || !popupContent.length) return; 56 57 // Create popup wrapper (creates both background and wrapper) 58 const popupWrapper = this.createPopupWrapper(config); 59 const popupContainer = this.createPopupContainer(config); 60 61 // Clone content and make it visible 62 const clonedContent = popupContent.clone(); 63 clonedContent.css('display', 'block'); 64 65 // Divi 5 lazy-loads video iframes (src moved to data-src, restored by an 66 // IntersectionObserver when scrolled into view). That observer can never 67 // fire for popup content: the holder is display:none, then removed, and 68 // each open injects a fresh clone the observer has never seen. Opening 69 // the popup IS the visibility signal, so restore the real src here. 70 clonedContent.find('iframe[data-src], source[data-src]').each(function () { 71 this.setAttribute('src', this.getAttribute('data-src')); 72 this.removeAttribute('data-src'); 73 }); 74 75 // Apply popup position classes 76 this.applyPopupPosition(popupContainer, clonedContent); 77 78 popupContainer.prepend(clonedContent); 79 80 // Add close button if needed - for inline popups, add to wrapper not content 81 if (config.closeMarkup && !config.closeBtnInside) { 82 popupWrapper.prepend(config.closeMarkup); 83 } else if (config.closeMarkup && config.closeBtnInside) { 84 // how to check already exit button or not 85 if (popupContainer.children().last().find('.dnxte_popup_close').length < 1) { 86 popupContainer.children().last().prepend(config.closeMarkup); 87 } 88 } 89 90 // Append container to wrapper's dnxte_popup_container 91 popupWrapper.find('.dnxte_popup_container').append(popupContainer); 92 93 // Store current popup reference (maintain backward compatibility) 94 this.currentPopup = popupWrapper; 95 // Also add to activePopups array 96 this.activePopups.push(popupWrapper); 97 98 // Re-initialize countdown timers in the popup 99 this.initializeCountdownTimers(clonedContent); 100 101 // Trigger beforeOpen callback 102 if (config.callbacks && config.callbacks.beforeOpen) { 103 config.callbacks.beforeOpen(); 104 } 105 106 // Bind events 107 this.bindEvents(popupWrapper, config); 108 109 // Prevent body scroll if needed 110 if (config.fixedContentPos) { 111 $('body').addClass('dnxte-popup-open'); 112 } 113 114 // Re-initialize Divi element links on the cloned popup content 115 // Cloned elements don't carry jQuery event handlers, so Divi's 116 // element-link click bindings (from diviElementLinkData) are lost. 117 // if (typeof window.et_pb_init_link === 'function') { 118 // window.et_pb_init_link(); 119 // } 120 }, 121 122 close: function (specificPopup = null) { 123 const popup = specificPopup || this?.currentPopup; 124 if (!popup) return; 125 126 const bgOverlay = popup?.data('bg-overlay'); 127 const config = popup?.data('popup-config'); 128 129 // Trigger beforeClose callback 130 if (config && config.callbacks && config.callbacks.beforeClose) { 131 config.callbacks.beforeClose(); 132 } 133 134 // Add closing animation classes 135 popup?.addClass('dnxte_popup-removing'); 136 if (bgOverlay) { 137 bgOverlay?.addClass('dnxte_popup-removing'); 138 } 139 140 // Remove after animation 141 setTimeout(() => { 142 popup?.remove(); 143 if (bgOverlay) { 144 bgOverlay.remove(); 145 } 146 147 // Remove from activePopups array 148 const index = this.activePopups.indexOf(popup); 149 if (index > -1) { 150 this.activePopups.splice(index, 1); 151 } 152 153 // Update currentPopup reference 154 if (this.currentPopup === popup) { 155 this.currentPopup = this.activePopups.length > 0 ? this.activePopups[this.activePopups.length - 1] : null; 156 } 157 158 // Only remove body class if no popups are active 159 if (this.activePopups.length === 0) { 160 $('body').removeClass('dnxte-popup-open'); 161 } 162 163 // Trigger afterClose callback 164 if (config && config.callbacks && config.callbacks.afterClose) { 165 config.callbacks.afterClose(); 166 } 167 }, config ? config.removalDelay || 1000 : 1000); 168 }, 169 170 createPopupWrapper: function (config) { 171 172 // Create background overlay with exact magnific popup classes 173 const bgOverlay = $('<div>', { 174 class: `dnxte_popup_overlay ${config.mainClass || ''} animate__animated`, 175 }); 176 177 // Create popup wrapper with exact magnific popup classes
178 const wrapper = $('<div>', { 179 class: `dnxte_popup_wrap ${config.mainClass || ''}`, 180 }); 181 182 // Create container 183 const container = $('<div>', { 184 class: 'dnxte_popup_container' 185 }); 186 wrapper.append(bgOverlay); 187 wrapper.append(container); 188 189 // Add both background and wrapper to body 190 $('body').prepend(wrapper); 191 //document.body.insertAdjacentHTML("afterbegin", wrapper); 192 193 wrapper.data('popup-config', config); 194 wrapper.data('bg-overlay', bgOverlay); 195 return wrapper; 196 }, 197 198 createPopupContainer: function (config) { 199 return $('<div>', { 200 class: 'dnxte_popup-content' 201 }); 202 }, 203 204 205 206 applyPopupPosition: function (container, content) { 207 // Check if it's full width layout 208 const isFullWidth = content.hasClass('dnxte_popup_pro_layout_full_width'); 209 210 if (isFullWidth) { 211 // For full width, make content fill the entire viewport 212 container.css({ 213 width: '100%', 214 height: '100%', 215 maxWidth: '100vw', 216 maxHeight: '100vh' 217 }); 218 content.css({ 219 width: '100%', 220 height: '100%' 221 }); 222 } else { 223 // Extract position classes from content 224 const className = content.attr('class') || ''; 225 const positionClasses = className.match(/dnxte_popup_pro_position_[\w-]+/); 226 if (positionClasses) { 227 const positionClass = positionClasses[0]; 228 const position = positionClass.replace('dnxte_popup_pro_position_', '').replace(/-/g, '_'); 229 230 // Apply position-specific styling to container 231 switch (position) { 232 case 'top_left': 233 container.parent().css({ justifyContent: 'flex-start', alignItems: 'flex-start' }); 234 break; 235 case 'top_center': 236 container.parent().css({ justifyContent: 'center', alignItems: 'flex-start' }); 237 break; 238 case 'top_right': 239 container.parent().css({ justifyContent: 'flex-end', alignItems: 'flex-start' }); 240 break; 241 case 'center_left': 242 container.parent().css({ justifyContent: 'flex-start', alignItems: 'center' }); 243 break; 244 case 'center_center': 245 container.parent().css({ justifyContent: 'center', alignItems: 'center' }); 246 break; 247 case 'center_right': 248 container.parent().css({ justifyContent: 'flex-end', alignItems: 'center' }); 249 break; 250 case 'bottom_left': 251 container.parent().css({ justifyContent: 'flex-start', alignItems: 'flex-end' }); 252 content.css({ left: '', right: '', top: '', bottom: '', transform: '' }); 253 break; 254 case 'bottom_center': 255 container.parent().css({ justifyContent: 'center', alignItems: 'flex-end' }); 256 break; 257 case 'bottom_right': 258 container.parent().css({ justifyContent: 'flex-end', alignItems: 'flex-end' }); 259 break; 260 } 261 } 262 } 263 }, 264 265 // Initialize countdown timers in popup content 266 initializeCountdownTimers: function (popupContent) { 267 const self = this; 268 269 // Find all countdown timer elements in the popup 270 const countdownTimers = popupContent.find('.et_pb_countdown_timer'); 271 272 if (countdownTimers.length > 0) { 273 274 countdownTimers.each(function () { 275 const $timer = $(this); 276 277 // Clear any existing intervals 278 const existingInterval = $timer.data('interval'); 279 if (existingInterval) { 280 clearInterval(existingInterval); 281 } 282 283 // Try Divi's native countdown initialization first 284 if (typeof window.et_pb_countdown_timer_init === 'function') { 285 try { 286 window.et_pb_countdown_timer_init($timer); 287 } catch (e) { 288 self.fallbackCountdownInit($timer); 289 } 290 } else if (typeof window.et_countdown_timer === 'function') { 291 try { 292 // Use et_countdown_timer function 293 window.et_countdown_timer($timer); 294 295 // Set up interval for continuous updates 296 const interval = setInterval(function () { 297 window.et_countdown_timer($timer); 298 }, 1000); 299 300 $timer.data('interval', interval); 301 } catch (e) { 302 self.fallbackCountdownInit($timer); 303 } 304 } else { 305 // Use fallback initialization 306 self.fallbackCountdownInit($timer); 307 } 308 }); 309 } 310 }, 311 312 // Fallback countdown initialization 313 fallbackCountdownInit: function ($timer) { 314 const endTimestamp = $timer.data('end-timestamp'); 315 316 if (!endTimestamp) { 317 return; 318 } 319 320 321 const updateCountdown = function () { 322 const now = Math.floor(Date.now() / 1000); 323 const timeLeft = endTimestamp - now; 324 325 if (timeLeft <= 0) { 326 // Countdown finished
327 $timer.find('.value').text('00'); 328 return; 329 } 330 331 const days = Math.floor(timeLeft / (60 * 60 * 24)); 332 const hours = Math.floor((timeLeft % (60 * 60 * 24)) / (60 * 60)); 333 const minutes = Math.floor((timeLeft % (60 * 60)) / 60); 334 const seconds = timeLeft % 60; 335 336 // Update display 337 $timer.find('.days .value').text(String(days).padStart(3, '0')); 338 $timer.find('.hours .value').text(String(hours).padStart(2, '0')); 339 $timer.find('.minutes .value').text(String(minutes).padStart(2, '0')); 340 $timer.find('.seconds .value').text(String(seconds).padStart(2, '0')); 341 }; 342 343 // Initial update 344 updateCountdown(); 345 346 // Set up interval 347 const interval = setInterval(updateCountdown, 1000); 348 $timer.data('interval', interval); 349 }, 350 351 bindEvents: function (wrapper, config) { 352 const self = this; 353 const bgOverlay = wrapper.data('bg-overlay'); 354 355 // Close on overlay click - bind to both wrapper and background 356 if (config.closeOnBgClick) { 357 // Bind to wrapper container click 358 wrapper.on('click', function (e) { 359 if (e.target === this || $(e.target).hasClass('dnxte_popup_container')) { 360 self.close(wrapper); 361 } 362 }); 363 364 // Bind to background overlay click 365 if (bgOverlay) { 366 bgOverlay.on('click', function (e) { 367 self.close(wrapper); 368 }); 369 } 370 } 371 // Close button click â route to close() so the closing animation 372 // (class swap in beforeClose) plays for the full removalDelay before 373 // the popup is removed. Previously this handler set a 500ms inline 374 // animation but removed the element at 200ms, cutting the animation off. 375 const idMatch = config.closeMarkup ? config.closeMarkup.match(/id="popup_close_(\d+)"/) : null; 376 const id = idMatch ? idMatch[1] : null; 377 let closeButton = wrapper.find(`#popup_close_${id}`); 378 379 closeButton.on('click', function (e) { 380 e.preventDefault(); 381 self.close(wrapper); 382 }); 383 384 // Custom close trigger â any element inside the popup matching the 385 // user-provided selector closes the popup (works alongside the default 386 // close button). A bare value (no . or #) matches either an id or a class. 387 if (config.closeTriggerSelector) { 388 let closeSel = String(config.closeTriggerSelector).trim(); 389 if (closeSel) { 390 if (!closeSel.startsWith('.') && !closeSel.startsWith('#')) { 391 closeSel = '#' + closeSel + ', .' + closeSel; 392 } 393 wrapper.on('click', closeSel, function (e) { 394 e.preventDefault(); 395 self.close(wrapper); 396 }); 397 } 398 } 399 400 // ESC key - close the most recently opened popup 401 $(document).on('keydown.dnxte-popup', function (e) { 402 if (e.keyCode === 27 && self.activePopups.length > 0) { 403 // Close the most recent popup 404 const mostRecentPopup = self.activePopups[self.activePopups.length - 1]; 405 self.close(mostRecentPopup); 406 } 407 }); 408 409 410 411 // Close popup by clicking back button ======== 412 let allPopups = $(".dnxte_popup_wrap"); 413 allPopups.each(function () { 414 let ppID = $(this).find('.dnxte_popup_content').data("popup-id"); 415 const ppOptions = ppID?.data()?.popup; 416 const clickOnBack = ppOptions?.close_clicking_back_button; 417 418 419 if ('true' === clickOnBack) { 420 window.history.pushState(null, null, ""); 421 window.history.pushState(null, null, ""); 422 window.history.pushState(null, null, ""); 423 $(window).on("popstate", function (e) { 424 e.preventDefault(); 425 426 const ppWrapper = $(`.dnxte_popup_custom_${ppID}`); 427 const ppContent = ppWrapper.find('.dnxte_popup_content'); 428 if (ppWrapper.length > 0) { 429 ppContent.css('animation', `500ms ease 0s 1 normal none running dnxte_popup_pro_${ppOptions?.closing_animation}`); 430 setTimeout(function () { 431 ppWrapper.remove(); 432 433 // Remove from activePopups array 434 const index = self.activePopups.indexOf(ppWrapper); 435 if (index > -1) { 436 self.activePopups.splice(index, 1); 437 } 438 439 // Update currentPopup reference 440 if (self.currentPopup === ppWrapper) { 441 self.currentPopup = self.activePopups.length > 0 ? self.activePopups[self.activePopups.length - 1] : null; 442 } 443 444 // Only remove body class if no popups are active 445 if (self.activePopups.length === 0) { 446 $('body').removeClass('dnxte-popup-open'); 447 } 448 }, 500); 449 } 450 451 }); 452 } 453 }); 454 455 } 456 }; 457
458 // Replace magnificPopup with our custom implementation 459 if (!window.jQuery.magnificPopup) { 460 $.magnificPopup = DnxteCustomPopup; 461 } 462 463 $(document).ready(function ($) { 464 if ("undefined" == typeof popup_frontend) return false; 465 466 var popup_json = JSON.parse(popup_frontend?.dnxte_popup_show); 467 popupIds = popup_json?.popup_ids; 468 469 // Helper function to set cookie when popup is triggered 470 function setPopupCookie(popupId) { 471 const popupShow = JSON.parse(popup_frontend.dnxte_popup_show); 472 const periodicity = popupShow[`popup_perodicity${popupId}`]; 473 474 if (periodicity !== "every_time" && !document.cookie.includes(`dxnteHours${popupId}=`)) { 475 var expires = new Date(); 476 expires.setTime(expires.getTime() + popupShow[`periodicity_cookie${popupId}`]); 477 document.cookie = `dxnteHours${popupId}=true; expires=` + expires.toUTCString() + "; path=/;"; 478 } 479 } 480 481 // Helper function to check if popup should be shown based on cookie 482 function canShowPopup(popupId) { 483 const popupShow = JSON.parse(popup_frontend.dnxte_popup_show); 484 const periodicity = popupShow[`popup_perodicity${popupId}`]; 485 486 if (periodicity === "every_time") { 487 return true; 488 } 489 490 return !document.cookie.includes(`dxnteHours${popupId}=`); 491 } 492 493 popupIds.forEach((id) => { 494 const data = JSON.parse(popup_json[`json_data${id}`]); 495 const content = $(`.dnxte_popup_content_${id}`); 496 const close_btn_tooltip = content.data("close-btn-tooltip") ? `data-tooltip="${escapeAttr(content.data("close-btn-tooltip"))}"` : ''; 497 const tooltipClass = content.data("close-btn-tooltip") ? ` close-btn-tooltip` : ''; 498 499 if ("true" === data?.remove_link) { 500 $(`a#popup_${id}`).removeAttr("href"); 501 } 502 503 504 let preventScrolling = 505 "true" === data.prevent_page_scrolling ? `dnxte-prevent-scrolling` : ""; 506 507 const clickable_under_overlay = 508 "false" === data.close_overlay_click 509 ? data.clickable_under_overlay 510 : false; 511 512 const popupConfig = { 513 items: { 514 src: `.dnxte_popup_content_${id}`, 515 // src: content, 516 }, 517 callbacks: { 518 beforeClose: function () { 519 $(`.dnxte_popup_content.dnxte_popup_content_${id}`) 520 .removeClass(`dnxte_popup_pro_${data.opening_animation}`) 521 .addClass(`dnxte_popup_pro_${data.closing_animation}`); 522 }, 523 afterClose: function () { 524 $(`.dnxte_popup_content.dnxte_popup_content_${id}`) 525 .removeClass(`dnxte_popup_pro_${data.closing_animation}`) 526 .addClass(`dnxte_popup_pro_${data.opening_animation}`); 527 }, 528 }, 529 type: "inline", 530 closeBtnInside: !JSON.parse(data?.close_btn_outside?.toLowerCase()), 531 closeOnBgClick: JSON.parse(data.close_overlay_click.toLowerCase()), 532 closeTriggerSelector: data.close_trigger_selector, 533 fixedContentPos: JSON.parse(data.prevent_page_scrolling.toLowerCase()), 534 mainClass: `dnxte_popup_custom ${clickable_under_overlay} ${preventScrolling} dnxte_popup_custom_${id}`, // mainClass: `dnxte_popup_custom ${data.clickable_under_overlay} dnxte_popup_custom_${id}`, 535 removalDelay: 1500, 536 midClick: true, 537 closeMarkup: 538 "false" === data.hide_close_button 539 ? `<button type="button" ${close_btn_tooltip} data-popup-id="${escapeAttr(id)}" id="popup_close_${escapeAttr(id)}" class="custom-tooltip dnxte_popup-close ${sanitizeClassToken(data.closing_css_selector)} dnxte_popup_pro_${sanitizeClassToken(data.close_btn_position)}_close ${tooltipClass}">×</button>` 540 : ``, 541 }; 542 543 if ("on" == data.dnxte_disable_desktop && 981 < $(window).width()) { 544 return; 545 } 546 if ( 547 "on" == data.dnxte_disable_tablet && 548 768 <= $(window).width() && 549 981 >= $(window).width() 550 ) { 551 return; 552 } 553 if ("on" == data.dnxte_disable_phone && 768 > $(window).width()) { 554 return; 555 } 556 557 var dnxte_popup_show = JSON.parse(popup_frontend.dnxte_popup_show); 558 559 let startTime = 0; 560 561 if ("" !== data?.trigger_delay_start) { 562 startTime = +data?.trigger_delay_start * 1000; 563 } 564 const endTime = startTime + data?.trigger_delay_end * 1000; 565 566 // Check if popup should be shown based on periodicity - but don't set cookie yet 567 if ("trigger_on_none" !== data?.trigger_event) { 568 // Only check if popup should show, don't set cookie until popup is actually triggered 569 if (!dnxte_popup_show[`popup_show${id}`]) return false;
570 571 // Remove cookie if periodicity is "every_time" 572 if ("every_time" == dnxte_popup_show[`popup_perodicity${id}`]) { 573 if (document.cookie.includes(`dxnteHours${id}=`)) { 574 document.cookie = `dxnteHours${id}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; 575 } 576 } 577 } 578 var trigger_event = JSON.parse(popup_frontend.trigger_event); 579 580 if ("trigger_on_none" === data.trigger_event) { 581 $(`#popup_${id}`).on("click", function (event) { 582 event.preventDefault(); 583 584 if ($(`body > .dnxte_popup_content_${id}`).length === 0 && canShowPopup(id)) { 585 DnxteCustomPopup.open(popupConfig); 586 setPopupCookie(id); 587 } 588 }); 589 590 /* custom css selector */ 591 if ("" == dnxte_popup_show[`custom_selector${id}`]) return false; 592 593 let custom_selector = dnxte_popup_show[`custom_selector${id}`]; 594 if (!custom_selector.startsWith('.')) { 595 custom_selector = '.' + custom_selector; 596 } 597 598 $(custom_selector).each(function () { 599 $(this).on("click", function (event) { 600 event.preventDefault(); 601 if (canShowPopup(id)) { 602 DnxteCustomPopup.open(popupConfig); 603 setPopupCookie(id); 604 } 605 }, 606 ); 607 }); 608 } else if ("trigger_on_load" === data.trigger_event) { 609 // Check if popup can be shown based on periodicity before triggering 610 if (canShowPopup(id)) { 611 if (0 == startTime) { 612 DnxteCustomPopup.open(popupConfig); 613 setPopupCookie(id); 614 } else { 615 const popupOpenTimer = setTimeout(() => { 616 DnxteCustomPopup.open(popupConfig); 617 setPopupCookie(id); 618 clearTimeout(popupOpenTimer); 619 }, startTime); 620 } 621 if (startTime < endTime) { 622 const popupCloseTimer = setTimeout(() => { 623 DnxteCustomPopup.close(); 624 clearTimeout(popupCloseTimer); 625 }, endTime); 626 } 627 } 628 } else if ("trigger_on_scroll" === data.trigger_event) { 629 let scroll_event = trigger_event[id]; 630 var recentScroll = false; 631 $(window).on("scroll", function () { 632 // Only proceed if popup can be shown based on periodicity 633 if (!recentScroll && canShowPopup(id)) { 634 const scrollPosition = window.scrollY + window.innerHeight; 635 const totalHeight = document.documentElement.scrollHeight; 636 let isPopupShown = false; 637 638 if (scroll_event.onscroll_offset_units != "px") { 639 const scrollPercentage = (scrollPosition / totalHeight) * 100; 640 if (scrollPercentage >= scroll_event.onscroll_offset) { 641 isPopupShown = true; 642 recentScroll = true; 643 } 644 } else if (scroll_event.onscroll_offset_units == "px") { 645 if (window.scrollY >= scroll_event.onscroll_offset) { 646 isPopupShown = true; 647 recentScroll = true; 648 } 649 } 650 651 if (isPopupShown) { 652 DnxteCustomPopup.open(popupConfig); 653 setPopupCookie(id); 654 } 655 } 656 }); 657 } else if ("trigger_on_exit" === data.trigger_event) { 658 let exitPopupShown = false; 659 let exitIntentCooldown = false; 660 661 document.addEventListener("mousemove", function exitIntentHandler(e) { 662 // Check if popup can be shown based on periodicity 663 if (!canShowPopup(id)) return; 664 665 // For "every_time" periodicity, allow multiple triggers but with cooldown 666 const popupShow = JSON.parse(popup_frontend.dnxte_popup_show); 667 const periodicity = popupShow[`popup_perodicity${id}`]; 668 669 // If not "every_time", only show once per session 670 if (periodicity !== "every_time" && exitPopupShown) return; 671 672 // Prevent rapid firing with cooldown 673 if (exitIntentCooldown) return; 674 675 // Get current scroll position 676 var scroll = window.pageYOffset || document.documentElement.scrollTop; 677 678 // Trigger on exit intent (mouse moving to top of viewport) 679 if (e.pageY - scroll < 7) { 680 // Check if this specific exit popup is already showing 681 const isThisPopupActive = $(`.dnxte_popup_custom_${id}`).length > 0; 682 683 if (!isThisPopupActive) { 684 DnxteCustomPopup.open(popupConfig); 685 setPopupCookie(id); 686 exitPopupShown = true; 687 exitIntentCooldown = true; 688 689 // Set cooldown to prevent rapid firing (3 seconds) 690 setTimeout(() => { 691 exitIntentCooldown = false;
692 // Reset exitPopupShown for "every_time" periodicity 693 if (periodicity === "every_time") { 694 exitPopupShown = false; 695 } 696 }, 3000); 697 698 // Only remove event listener for non-"every_time" periodicity 699 if (periodicity !== "every_time") { 700 document.removeEventListener("mousemove", exitIntentHandler); 701 } 702 } 703 } 704 }); 705 } else if ("trigger_on_inactivity" === data.trigger_event) { 706 let inactivity_event = trigger_event[id]; 707 708 // Only set up inactivity timeout if popup can be shown 709 if (canShowPopup(id)) { 710 setIdleTimeout( 711 inactivity_event.oninactivity_delays * 1000, 712 function () { 713 if (canShowPopup(id)) { // Double check before showing 714 DnxteCustomPopup.open(popupConfig); 715 setPopupCookie(id); 716 } 717 }, 718 function () { 719 return false; 720 }, 721 ); 722 } 723 724 function setIdleTimeout(millis, onIdle, onUnidle) { 725 var timeout = 0; 726 startTimer(); 727 728 function startTimer() { 729 timeout = setTimeout(onExpires, millis); 730 document.addEventListener("mousemove", onActivity); 731 document.addEventListener("keydown", onActivity); 732 document.addEventListener("touchstart", onActivity); 733 } 734 735 function onExpires() { 736 timeout = 0; 737 onIdle(); 738 } 739 740 function onActivity() { 741 if (timeout) clearTimeout(timeout); 742 else onUnidle(); 743 //since the mouse is moving, we turn off our event hooks for 1 second 744 document.removeEventListener("mousemove", onActivity); 745 document.removeEventListener("keydown", onActivity); 746 document.removeEventListener("touchstart", onActivity); 747 setTimeout(startTimer, 1000); 748 } 749 } 750 } 751 return false; 752 }); 753 }); 754 755 $.initModules = function () { 756 const element = document.querySelector(".et_pb_section"); 757 if (element) { 758 const parent = element.parentElement; 759 if (parent) { 760 element.style.width = `${parent.offsetWidth}px`; 761 } 762 } 763 if (typeof et_pb_init_global_functions === "function") { 764 et_pb_init_global_functions(); 765 } 766 if (typeof et_pb_init_modules === "function") { 767 et_pb_init_modules(); 768 } 769 }; 770})(jQuery);
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.