1/* ============================================= 2 EXIT-INTENT POPUP â ÐоÑод ÐоÑог 3 Desktop: mouseleave ввеÑÑ 4 Mobile: visibilitychange / back-scroll 5 ÐÑлдаÑн: 72Ñ (localStorage) 6 Ðин. вÑÐµÐ¼Ñ Ð½Ð° ÑÑÑаниÑе: 10 Ñек 7 ФоÑма: инÑегÑиÑована Ñ form.js (.nbt + [action]) 8 ============================================= */ 9 10(function () { 11 'use strict'; 12 13 var STORAGE_KEY = 'gd_exit_intent'; 14 var COOLDOWN_MS = 72 * 60 * 60 * 1000; 15 var MIN_TIME_MS = 10000; 16 var pageLoadTime = Date.now(); 17 var shown = false; 18 19 function isOnCooldown() { 20 try { 21 var ts = localStorage.getItem(STORAGE_KEY); 22 if (!ts) return false; 23 return (Date.now() - parseInt(ts, 10)) < COOLDOWN_MS; 24 } catch (e) { return false; } 25 } 26 27 function setCooldown() { 28 try { localStorage.setItem(STORAGE_KEY, Date.now().toString()); } 29 catch (e) { /* ok */ } 30 } 31 32 function hasEnoughTime() { 33 return (Date.now() - pageLoadTime) >= MIN_TIME_MS; 34 } 35 36 /* ÐодÑÑгиваем акÑÑалÑнÑÑ Ð´Ð°ÑÑ Ð°ÐºÑии из hero на ÑÑÑаниÑе. 37 ÐÑем «до DD <меÑÑÑ>» â на главной ÑÑо .nbanner__title (динамика из админки), 38 на дÑÑÐ³Ð¸Ñ ÑÑÑаниÑÐ°Ñ â лÑбой ÑекÑÑ Ñ ÑÑой ÑоÑмой. ÐоÑмализÑем в «ТолÑко до DD <меÑÑÑ>». */ 39 function getSaleDeadline() { 40 var monthRe = /до\s+(\d{1,2})\s+(ÑнваÑÑ|ÑевÑалÑ|маÑÑа|апÑелÑ|маÑ|иÑнÑ|иÑлÑ|авгÑÑÑа|ÑенÑÑбÑÑ|окÑÑбÑÑ|ноÑбÑÑ|декабÑÑ)/i; 41 42 // 1. ÐÑиоÑиÑÐµÑ â главнÑй hero (.nbanner__title) 43 var hero = document.querySelector('.nbanner__title'); 44 if (hero) { 45 var m = (hero.textContent || '').match(monthRe); 46 if (m) return 'ТолÑко до ' + m[1] + ' ' + m[2].toLowerCase(); 47 } 48 49 // 2. Fallback â лÑбой leaf-Ñзел в DOM Ñ Ð¿Ð¾Ð´Ñ Ð¾Ð´ÑÑей даÑой 50 var nodes = document.querySelectorAll('b, strong, span, div, p, h1, h2, h3'); 51 for (var i = 0; i < nodes.length; i++) { 52 if (nodes[i].children.length > 0) continue; 53 var txt = (nodes[i].textContent || '').replace(/\s+/g, ' ').trim(); 54 var mm = txt.match(monthRe); 55 if (mm) return 'ТолÑко до ' + mm[1] + ' ' + mm[2].toLowerCase(); 56 } 57 58 // ÐдинÑй иÑÑоÑник даÑÑ â Ð½Ð°Ñ Ð¾Ð²ÐµÑÑайд в init.php: зеÑкало клиенÑÑкого 59 // getSaleDate() (ÑÐ¾Ñ Ð¶Ðµ /local/date.log и ÑÐ¾Ñ Ð¶Ðµ Ñаг P2W), поÑÑÐ¾Ð¼Ñ Ð´Ð°Ñа 60 // здеÑÑ Ð½Ðµ Ð¼Ð¾Ð¶ÐµÑ ÑазойÑиÑÑ Ñ Ð´Ð°Ñой на главной. ХаÑдкод â ÑолÑко на ÑлÑÑай, 61 // еÑли инÑекÑÐ¸Ñ Ð½Ðµ Ð´Ð¾ÐµÑ Ð°Ð»Ð°. 62 return 'ТолÑко до ' + (window.__gdSaleDeadline || '27 авгÑÑÑа'); 63 } 64 65 function createPopup() { 66 var currentLink = window.location.href; 67 var saleDeadline = getSaleDeadline(); 68 var overlay = document.createElement('div'); 69 overlay.className = 'ei-overlay'; 70 overlay.id = 'exitIntentPopup'; 71 overlay.innerHTML = 72 '<div class="ei-card">' + 73 '<div class="ei-photo">' + 74 '<img src="/local/chat-widget/img/exit-intent-photo.jpg" alt="ÐоÑод ÐоÑог â полÑÑиÑе пÑава" loading="lazy">' + 75 '</div>' + 76 '<div class="ei-content">' + 77 '<button class="ei-close" aria-label="ÐакÑÑÑÑ">×</button>' + 78 '<div class="ei-badge">' + saleDeadline + '</div>' + 79 '<h2 class="ei-title">Скидка 7 000 ⽠на обÑÑение</h2>' + 80 '<ul class="ei-list">' + 81 '<li>ÐÑава за 3 меÑÑÑа</li>' + 82 '<li>Ðез Ð´Ð¾Ð¿Ð»Ð°Ñ Ð·Ð° бензин</li>' + 83 '<li>СопÑовождаем до полÑÑÐµÐ½Ð¸Ñ Ð¿Ñав</li>' + 84 '</ul>' + 85 '<div class="ei-form" id="ei-form" action="/">' + 86 '<input type="text" name="user_name" placeholder="ÐаÑе имÑ" autocomplete="off" required>' + 87 '<input type="tel" name="MESSAGE" placeholder="+7 (___) ___-__-__" class="js-mask-phone" autocomplete="off" required>' + 88 '<input type="hidden" name="user_email" value="">' + 89 '<input type="hidden" name="recaptcha_token" value="">' + 90 '<input type="hidden" name="grec" value="test">' + 91 '<input type="hidden" name="AJAX" value="Y">' + 92 '<input type="hidden" name="action" value="form">' + 93 '<input type="hidden" name="goal" value="form_trial_lesson">' + 94 '<input type="hidden" name="FORM_NAME" value="Скидка (exit-intent)">' + 95 '<input type="hidden" name="CURRENT_LINK" value="' + currentLink + '">' + 96 '<div class="result"></div>' + 97 '<div class="nbt ei-btn">ÐолÑÑиÑÑ ÑкидкÑ</div>' + 98 '</div>' + 99 '<p class="ei-privacy">ÐÐ°Ð¶Ð¸Ð¼Ð°Ñ Ð½Ð° кнопкÑ, Ñ ÑоглаÑаÑÑÑ Ñ ÑÑловиÑми<br>' + 100 '<a href="/politika-konfidentsialnosti/" target="_blank">ÐолиÑики конÑиденÑиалÑноÑÑи</a></p>' + 101 '</div>' + 102 '</div>'; 103 104 document.body.appendChild(overlay); 105 106 // ÐаÑка ÑелеÑона â еÑли еÑÑÑ jQuery + maskedinput 107 setTimeout(function () { 108 try { 109 var phoneInput = overlay.querySelector('input[type="tel"]'); 110 if (phoneInput && window.jQuery && jQuery.fn.mask) { 111 jQuery(phoneInput).mask('+7 (999) 999-99-99'); 112 } 113 } catch (e) { /* ok */ } 114 }, 100); 115 116 return overlay; 117 } 118 119 function showPopup() { 120 if (shown || isOnCooldown() || !hasEnoughTime()) return; 121 shown = true;
122 setCooldown(); 123 124 var overlay = document.getElementById('exitIntentPopup') || createPopup(); 125 126 overlay.style.display = 'flex'; 127 overlay.offsetHeight; 128 overlay.classList.add('ei-overlay--visible'); 129 130 // Re-init form.js handlers for new .nbt button 131 if (typeof initEventClick === 'function') { 132 initEventClick(); 133 } 134 135 var closeBtn = overlay.querySelector('.ei-close'); 136 if (closeBtn) { 137 closeBtn.addEventListener('click', function () { hidePopup(overlay); }); 138 } 139 140 overlay.addEventListener('click', function (e) { 141 if (e.target === overlay) hidePopup(overlay); 142 }); 143 144 document.addEventListener('keydown', function onEsc(e) { 145 if (e.key === 'Escape') { 146 hidePopup(overlay); 147 document.removeEventListener('keydown', onEsc); 148 } 149 }); 150 } 151 152 function hidePopup(overlay) { 153 overlay.classList.remove('ei-overlay--visible'); 154 setTimeout(function () { 155 overlay.style.display = 'none'; 156 }, 350); 157 } 158 159 // --- Desktop: mouseleave top --- 160 function initDesktop() { 161 document.documentElement.addEventListener('mouseleave', function (e) { 162 if (e.clientY < 0) showPopup(); 163 }); 164 } 165 166 // --- Mobile: visibility + back-scroll --- 167 function initMobile() { 168 document.addEventListener('visibilitychange', function () { 169 if (document.visibilityState === 'hidden') { 170 if (!shown && !isOnCooldown() && hasEnoughTime()) { 171 document.addEventListener('visibilitychange', function onReturn() { 172 if (document.visibilityState === 'visible') { 173 document.removeEventListener('visibilitychange', onReturn); 174 setTimeout(showPopup, 500); 175 } 176 }); 177 } 178 } 179 }); 180 181 var lastScrollY = window.scrollY; 182 var scrollUpCount = 0; 183 184 window.addEventListener('scroll', function () { 185 var currentY = window.scrollY; 186 if (currentY < lastScrollY && currentY < 100 && lastScrollY > 300) { 187 scrollUpCount++; 188 if (scrollUpCount >= 2) showPopup(); 189 } else if (currentY > lastScrollY) { 190 scrollUpCount = 0; 191 } 192 lastScrollY = currentY; 193 }, { passive: true }); 194 } 195 196 function init() { 197 if (isOnCooldown()) return; 198 199 createPopup(); 200 201 var isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent) || 202 window.innerWidth < 768; 203 204 if (isMobile) { 205 initMobile(); 206 } else { 207 initDesktop(); 208 } 209 } 210 211 if (document.readyState === 'loading') { 212 document.addEventListener('DOMContentLoaded', init); 213 } else { 214 init(); 215 } 216})();
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.