PageSourceSearch

https://komplektadr.ru/local/js/kadr_home_routes.js?v=1786486839

js komplektadr.ru collected 2026-10-08 15:19:37 UTC 6,695 bytes, 140 lines download raw bytes

1/**
2 * Главная komplektadr.ru — блок маршрутов (#kadr-routes):
3 *  1) счётчики кликов: beacon в /local/ajax/un_track.php (action=home, un=слаг) + Метрика reachGoal
4 *     home_route_un / home_route_all (если счётчик загружен — он грузится после согласия на cookie);
5 *  2) одноразовая подсказка новичку: затемнение + подсветка блока + выноска.
6 *     Показывается один раз (localStorage kadr_home_hint_seen), только после реального действия
7 *     пользователя (скролл/мышь/тач — боты и скриншотер Google её не видят) и только когда
8 *     закрыта куки-плашка (#kadr-cookie-banner), чтобы не наслаивать два окна.
9 * Деплой: /local/js/kadr_home_routes.js; подключается из include/mainpage/components/float_banners/type_2.php
10 * с ?v=filemtime. Исходник: project6-Bitrix/kadr-pereezd/home-routes/.
11 */
12(function () {
13  'use strict';
14
15  var routes = document.getElementById('kadr-routes');
16  if (!routes) return;
17
18  // ---------- счётчики кликов ----------
19  function track(target) {
20    try {
21      var fd = new FormData();
22      fd.append('un', target);
23      fd.append('action', 'home');
24      if (navigator.sendBeacon) navigator.sendBeacon('/local/ajax/un_track.php', fd);
25      else fetch('/local/ajax/un_track.php', { method: 'POST', body: fd, keepalive: true });
26    } catch (e) {}
27    try {
28      if (typeof ym === 'function') ym(21641209, 'reachGoal', 'home_route_' + target);
29    } catch (e) {}
30  }
31  routes.addEventListener('click', function (ev) {
32    var a = ev.target.closest ? ev.target.closest('a[data-kadr-route]') : null;
33    if (a) track(a.getAttribute('data-kadr-route'));
34  });
35
36  // ---------- одноразовая подсказка ----------
37  var KEY = 'kadr_home_hint_seen';
38  try {
39    if (localStorage.getItem(KEY)) return;
40  } catch (e) { return; } // приватный режим без localStorage — подсказку не рискуем показывать каждый раз
41
42  function cookieBarVisible() {
43    var b = document.getElementById('kadr-cookie-banner');
44    return !!(b && b.style.display !== 'none' && b.offsetParent !== null);
45  }
46
47  var armed = false;
48  function arm() {
49    if (armed) return;
50    armed = true;
51    if (cookieBarVisible()) {
52      // ждём, пока посетитель закроет плашку про cookie; страховка — 12 с
53      var fired = false;
54      function go() { if (!fired) { fired = true; setTimeout(show, 600); } }
55      ['kadr-cookie-accept', 'kadr-cookie-reject'].forEach(function (id) {
56        var btn = document.getElementById(id);
57        if (btn) btn.addEventListener('click', go, { once: true });
58      });
59      setTimeout(go, 12000);
60    } else {
61      setTimeout(show, 900);
62    }
63  }
64  ['scroll', 'mousemove', 'touchstart', 'keydown'].forEach(function (evt) {
65    window.addEventListener(evt, arm, { once: true, passive: true });
66  });
67
68  var spot = null, tip = null;
69
70  function place() {
71    var r = routes.getBoundingClientRect();
72    if (spot) {
73      spot.style.top = (r.top - 2) + 'px';
74      spot.style.left = (r.left - 2) + 'px';
75      spot.style.width = (r.width + 4) + 'px';
76      spot.style.height = (r.height + 4) + 'px';
77    }
78    if (tip) {
79      var tw = tip.offsetWidth;
80      var th = tip.offsetHeight;
81      // сверху слева (над блоком — там только затемнённый заголовок);
82      // снизу — фолбэк, когда сверху не хватает места (мобайл, малые экраны)
83      var above = r.top - th - 14 >= 8;
84      tip.setAttribute('data-side', above ? 'above' : 'below');
85      tip.style.top = (above ? r.top - th - 14 : r.bottom + 14) + 'px';
86      tip.style.left = Math.max(16, Math.min(r.left, window.innerWidth - tw - 16)) + 'px';
87    }
88  }
89
90  function close() {
91    if (spot) { spot.remove(); spot = null; }
92    if (tip) { tip.remove(); tip = null; }
93    window.removeEventListener('resize', place);
94    window.removeEventListener('scroll', place);
95    window.removeEventListener('keydown', onKey);
96    document.removeEventListener('click', close);
97  }
98
99  function onKey(ev) { if (ev.key === 'Escape') close(); }
100
101  function show() {
102    if (spot) return;
103    try { localStorage.setItem(KEY, '1'); } catch (e) {}
104    // блок уже вне экрана (посетитель проскроллил дальше) — не дёргаем его обратно
105    var r = routes.getBoundingClientRect();
106    if (r.bottom < 0 || r.top > window.innerHeight) return;
107
108    // «прожектор»: прозрачное окно точно по блоку, тень вокруг затемняет страницу
109    spot = document.createElement('div');
110    spot.className = 'kadr-hint-spot';
111    spot.setAttribute('aria-hidden', 'true');
112    document.body.appendChild(spot);
113
114    tip = document.createElement('div');
115    tip.className = 'kadr-hint-tip';
116    tip.setAttribute('role', 'dialog');
117    tip.setAttribute('aria-label', 'Подсказка новому посетителю');
118    tip.innerHTML =
119      '<div class="kadr-hint-tip__kicker">Подсказка</div>' +
120      '<div class="kadr-hint-tip__title">С чего начать</div>' +
121      '<div class="kadr-hint-tip__text">
121Два пути: по номеру ООН из накладной — или по классам опасности, если груз ещё не известен.</div>' +
122      '<button type="button" class="kadr-hint-tip__ok">Понятно</button>';
123    document.body.appendChild(tip);
124    place();
125
126    // не rAF: в фоновой вкладке rAF заморожен и класс появления не навесится
127    setTimeout(function () {
128      if (spot) spot.classList.add('is-on');
129      if (tip) tip.classList.add('is-on');
130    }, 20);
131
132    tip.querySelector('.kadr-hint-tip__ok').addEventListener('click', close);
133    // прожектор кликов не ловит (pointer-events:none) — ссылки маршрутов живые;
134    // закрываем по любому клику на странице, Escape или «Понятно»
135    setTimeout(function () { document.addEventListener('click', close); }, 0);
136    window.addEventListener('keydown', onKey);
137    window.addEventListener('resize', place);
138    window.addEventListener('scroll', place, { passive: true });
139  }
140})();

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.