1/* PRIVACY PATCH 20260914 */ 2(() => { 3 const phoneHref = "tel:+74950218385"; 4 const modal = document.getElementById("requestModal"); 5 const form = modal ? modal.querySelector(".request-form") : null; 6 const isMobile = () => window.matchMedia("(max-width: 760px)").matches; 7 const storageKey = "dreame_traffic_source"; 8 const sessionKey = "seo_direct_session_id"; 9 10 const getSessionId = () => { 11 let sessionId = localStorage.getItem(sessionKey); 12 if (!sessionId) { 13 sessionId = window.crypto && typeof window.crypto.randomUUID === "function" 14 ? window.crypto.randomUUID() 15 : `${Date.now()}-${Math.random().toString(16).slice(2)}`; 16 localStorage.setItem(sessionKey, sessionId); 17 } 18 return sessionId; 19 }; 20 21 const isInternalReferrer = (referrer) => { 22 if (!referrer) return false; 23 try { 24 return new URL(referrer).hostname.replace(/^www\./, "") === location.hostname.replace(/^www\./, ""); 25 } catch (error) { 26 return false; 27 } 28 }; 29 30 const getTrafficData = () => { 31 const params = new URLSearchParams(location.search); 32 const referrer = document.referrer || ""; 33 const utmSource = params.get("utm_source") || ""; 34 const utmMedium = params.get("utm_medium") || ""; 35 let trafficSource = ""; 36 37 if (params.has("yclid") || (/yandex/i.test(utmSource) && /cpc|ppc|paid|direct/i.test(utmMedium))) { 38 trafficSource = "Ð¯Ð½Ð´ÐµÐºÑ ÐиÑекÑ"; 39 } else if (params.has("gclid") || params.has("gbraid") || params.has("wbraid") || (/google/i.test(utmSource) && /cpc|ppc|paid/i.test(utmMedium))) { 40 trafficSource = "Google Ads"; 41 } else if (/yandex\./i.test(referrer)) { 42 trafficSource = "Ð¯Ð½Ð´ÐµÐºÑ SEO"; 43 } else if (/google\./i.test(referrer)) { 44 trafficSource = "Google SEO"; 45 } else if (utmSource) { 46 trafficSource = [utmSource, utmMedium].filter(Boolean).join(" / "); 47 } else if (referrer) { 48 trafficSource = "ÐеÑÐµÑ Ð¾Ð´ Ñ ÑайÑа"; 49 } else { 50 trafficSource = "ÐÑÑмой Ð·Ð°Ñ Ð¾Ð´"; 51 } 52 53 const data = { 54 traffic_source: trafficSource, 55 utm_source: utmSource, 56 utm_medium: utmMedium, 57 utm_campaign: params.get("utm_campaign") || "", 58 utm_term: params.get("utm_term") || "", 59 utm_content: params.get("utm_content") || "", 60 referrer, 61 yclid: params.get("yclid") || "", 62 gclid: params.get("gclid") || "", 63 }; 64 const saved = JSON.parse(localStorage.getItem(storageKey) || "null"); 65 const hasCampaign = params.toString(); 66 const hasExternalReferrer = referrer && !isInternalReferrer(referrer); 67 if (!saved || hasCampaign || hasExternalReferrer) localStorage.setItem(storageKey, JSON.stringify(data)); 68 return saved && !hasCampaign && !hasExternalReferrer ? saved : data; 69 }; 70 71 const fillTrafficFields = () => { 72 if (!form) return; 73 const data = getTrafficData(); 74 Object.entries(data).forEach(([key, value]) => { 75 const input = form.elements[key]; 76 if (input) input.value = value || ""; 77 }); 78 }; 79 80 const refreshCaptcha = () => { 81 if (!form) return; 82 const a = Math.floor(Math.random() * 8) + 2; 83 const b = Math.floor(Math.random() * 8) + 2; 84 const question = form.querySelector("[data-captcha-question]"); 85 if (question) question.textContent = "ÐÑовеÑка: " + a + " + " + b + " ="; 86 if (form.elements.captcha_a) form.elements.captcha_a.value = String(a); 87 if (form.elements.captcha_b) form.elements.captcha_b.value = String(b); 88 if (form.elements.captcha) form.elements.captcha.value = ""; 89 }; 90 91 const openPopup = (button) => { 92 if (!modal || !form) return; 93 fillTrafficFields(); 94 refreshCaptcha(); 95 form.page.value = location.href; 96 form.source.value = button?.dataset.source || "Ðнопка"; 97 form.model.value = button?.dataset.model || ""; 98 form.device_type.value = button?.dataset.deviceType || ""; 99 modal.classList.add("is-open"); 100 modal.setAttribute("aria-hidden", "false"); 101 const input = form.querySelector("input[name='phone']"); 102 if (input) setTimeout(() => input.focus(), 50); 103 }; 104 105 const closePopup = () => { 106 if (!modal) return; 107 modal.classList.remove("is-open"); 108 modal.setAttribute("aria-hidden", "true"); 109 }; 110 111 const sendAnalyticsPhoneClick = (data) => { 112 const payload = JSON.stringify({ 113 site: location.hostname.replace(/^www\./, ""), 114 session: getSessionId(), 115 source: data.traffic_source || "", 116 event: "phone_click", 117 page: location.pathname + location.search, 118 url: location.href, 119 data, 120 }); 121 const endpoint = "https://tracker.xiaomi-remont.ru/api/event"; 122 window.__seoDirectSkipNextPhoneClick = true; 123 124 if (navigator.sendBeacon) { 125 const blob = new Blob([payload], { type: "application/json" }); 126 if (navigator.sendBeacon(endpoint, blob)) return; 127 } 128 fetch(endpoint, { 129 method: "POST", 130 headers: { "Content-Type": "application/json" }, 131 body: payload, 132 keepalive: true, 133 credentials: "omit", 134 }).catch(() => {}); 135 }; 136 137 const trackPhoneClick = (link, fallbackName = "СÑÑлка ÑелеÑона") => { 138 const data = getTrafficData(); 139 const payload = new FormData(); 140 const href = link?.getAttribute?.("href") || phoneHref; 141 const displayPhone = link?.textContent?.trim() || fallbackName; 142 payload.append("page", location.href); 143 payload.append("session_id", getSessionId()); 144 payload.append("display_phone", displayPhone); 145 payload.append("href_phone", href); 146 payload.append("click_place", link?.closest?.(".site-header") ? "Шапка" : link?.closest?.(".mobile-bar") ? "ÐобилÑÐ½Ð°Ñ Ð½Ð¸Ð¶Ð½ÑÑ Ð¿Ð°Ð½ÐµÐ»Ñ" : link?.closest?.(".contacts-page") ? "ÐонÑакÑÑ" : fallbackName);
147 Object.entries(data).forEach(([key, value]) => payload.append(key, value || "")); 148 149 if (navigator.sendBeacon) { 150 navigator.sendBeacon("/call-track.php", payload); 151 } else { 152 fetch("/call-track.php", { method: "POST", body: payload, keepalive: true }).catch(() => {}); 153 } 154 155 sendAnalyticsPhoneClick({ 156 ...data, 157 button_name: displayPhone, 158 href, 159 phone: displayPhone, 160 referrer: data.referrer || document.referrer || "", 161 landing_url: location.href, 162 }); 163 }; 164 165 document.addEventListener("click", (event) => { 166 const phoneLink = event.target.closest("a[href^='tel:']"); 167 if (phoneLink) trackPhoneClick(phoneLink); 168 169 const popupButton = event.target.closest(".js-open-popup,.price-action"); 170 if (popupButton) { 171 event.preventDefault(); 172 if (isMobile() && popupButton.classList.contains("price-action")) { 173 trackPhoneClick(null, popupButton.textContent.trim() || "Ðнопка ÑенÑ"); 174 location.href = phoneHref; 175 return; 176 } 177 openPopup(popupButton); 178 return; 179 } 180 181 const mobilePrice = event.target.closest(".table-wrap.active td:nth-child(3)"); 182 if (mobilePrice && isMobile()) { 183 event.preventDefault(); 184 trackPhoneClick(null, mobilePrice.textContent.trim() || "СÑÑока ÑенÑ"); 185 location.href = phoneHref; 186 return; 187 } 188 189 const modelButton = event.target.closest(".js-model-action"); 190 if (modelButton) { 191 event.preventDefault(); 192 if (isMobile()) { 193 trackPhoneClick(null, modelButton.textContent.trim() || "Ðнопка модели"); 194 location.href = phoneHref; 195 } else openPopup(modelButton); 196 return; 197 } 198 199 if (event.target.closest("[data-close-modal]")) closePopup(); 200 }); 201 202 document.addEventListener("keydown", (event) => { 203 if (event.key === "Escape") closePopup(); 204 }); 205 206 document.querySelectorAll(".district-shell").forEach((shell) => { 207 const buttons = shell.querySelectorAll("[data-district]"); 208 const panels = shell.querySelectorAll("[data-district-panel]"); 209 buttons.forEach((button) => { 210 button.addEventListener("click", () => { 211 buttons.forEach((item) => item.classList.toggle("active", item === button)); 212 panels.forEach((panel) => panel.classList.toggle("active", panel.dataset.districtPanel === button.dataset.district)); 213 }); 214 }); 215 }); 216 217 document.querySelectorAll(".price-section").forEach((section) => { 218 const buttons = section.querySelectorAll("[data-price-tab]"); 219 const panels = section.querySelectorAll("[data-price-panel]"); 220 const select = section.querySelector("[data-price-select]"); 221 const toggle = section.querySelector(".price-select-toggle"); 222 const current = section.querySelector(".price-select-current"); 223 if (toggle && select) { 224 toggle.addEventListener("click", () => { 225 const open = !select.classList.contains("open"); 226 select.classList.toggle("open", open); 227 toggle.setAttribute("aria-expanded", open ? "true" : "false"); 228 }); 229 document.addEventListener("click", (event) => { 230 if (!select.contains(event.target)) { 231 select.classList.remove("open"); 232 toggle.setAttribute("aria-expanded", "false"); 233 } 234 }); 235 } 236 buttons.forEach((button) => { 237 button.addEventListener("click", () => { 238 buttons.forEach((item) => { 239 const active = item === button; 240 item.classList.toggle("active", active); 241 item.setAttribute("aria-selected", active ? "true" : "false"); 242 }); 243 panels.forEach((panel) => { 244 panel.classList.toggle("active", panel.dataset.pricePanel === button.dataset.priceTab); 245 }); 246 if (current) current.textContent = button.textContent.trim(); 247 if (select && toggle) { 248 select.classList.remove("open"); 249 toggle.setAttribute("aria-expanded", "false"); 250 } 251 }); 252 }); 253 }); 254 255 if (form) { 256 fillTrafficFields(); 257 refreshCaptcha(); 258 form.addEventListener("submit", async (event) => { 259 event.preventDefault(); 260 const status = form.querySelector("[data-form-note]"); 261 const captcha = Number(form.elements.captcha?.value || NaN); 262 const expected = Number(form.elements.captcha_a?.value || 0
262) + Number(form.elements.captcha_b?.value || 0); 263 if (!Number.isFinite(captcha) || captcha !== expected) { 264 if (status) status.textContent = "ÐÑовеÑÑÑе оÑÐ²ÐµÑ Ð½Ð° капÑÑ."; 265 refreshCaptcha(); 266 return; 267 } 268 fillTrafficFields(); 269 if (status) status.textContent = "ÐÑпÑавлÑем заÑвкÑ..."; 270 try { 271 const response = await fetch(form.action, { method: "POST", body: new FormData(form), headers: { Accept: "application/json" } }); 272 const payload = await response.json().catch(() => ({})); 273 if (!response.ok) { 274 if (response.status === 429 && status) { 275 const minutes = Math.max(1, Math.ceil(Number(payload.retry_after || 1800) / 60)); 276 status.textContent = `ÐаÑвка Ñже оÑпÑавлена. ÐовÑоÑÐ½Ð°Ñ Ð¾ÑпÑавка бÑÐ´ÐµÑ Ð´Ð¾ÑÑÑпна пÑимеÑно ÑеÑез ${minutes} мин.`; 277 return; 278 } 279 throw new Error(payload.error || "send failed"); 280 } 281 form.reset(); 282 window.location.href = "/spasibo/"; 283 } catch (error) { 284 if (status) status.textContent = "Ðе ÑдалоÑÑ Ð¾ÑпÑавиÑÑ Ð·Ð°ÑвкÑ. ÐозвониÑе нам или попÑобÑйÑе еÑе Ñаз."; 285 } 286 }); 287 } 288 289 const pad = (value) => String(value).padStart(2, "0"); 290 const updateCountdown = () => { 291 const now = new Date(); 292 const end = new Date(now); 293 end.setHours(23, 59, 59, 999); 294 let diff = Math.max(0, end - now); 295 const hours = Math.floor(diff / 3600000); 296 diff -= hours * 3600000; 297 const minutes = Math.floor(diff / 60000); 298 diff -= minutes * 60000; 299 const seconds = Math.floor(diff / 1000); 300 document.querySelectorAll("[data-countdown]").forEach((node) => { 301 node.textContent = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; 302 }); 303 }; 304 updateCountdown(); 305 setInterval(updateCountdown, 1000); 306})(); 307;(() => { let started = false; const start = () => { if (started) return; started = true; 308;(() => { 309 if (window.__seoDirectTrackerLoader) return; 310 window.__seoDirectTrackerLoader = true; 311 const script = document.createElement("script"); 312 script.src = "https://tracker.xiaomi-remont.ru/tracker.js"; 313 script.defer = true; 314 document.head.appendChild(script); 315})(); 316}; if (window.sitePrivacyAnalyticsAllowed && window.sitePrivacyAnalyticsAllowed()) start(); else window.addEventListener("privacy:analytics-allowed", start, {once:true}); })(); 317 318 319;(() => { let started = false; const start = () => { if (started) return; started = true; 320;(()=>{const i="G-J0XFD6CEQ3";if(document.querySelector("script[data-ga4]"))return;window.dataLayer=window.dataLayer||[];window.gtag=window.gtag||function(){window.dataLayer.push(arguments)};const s=document.createElement("script");s.async=true;s.dataset.ga4=i;s.src="https://www.googletagmanager.com/gtag/js?id="+encodeURIComponent(i);document.head.appendChild(s);window.gtag("js",new Date());window.gtag("config",i)})(); 321}; if (window.sitePrivacyAnalyticsAllowed && window.sitePrivacyAnalyticsAllowed()) start(); else window.addEventListener("privacy:analytics-allowed", start, {once:true}); })(); 322
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.