PageSourceSearch

https://blaber.ai/assets/site.js?v=1790625334

js blaber.ai collected 2026-10-05 13:55:53 UTC 8,537 bytes, 190 lines download raw bytes

1/* Общий клиентский слой сайта: бургер-меню, мобильные фиксы, типографика, якоря.
2   Подключается на всех страницах (лендинги, блог, юр-страницы). */
3(function () {
4  var lastToggle = 0;
5  var LINKS = [
6    ['Отзывы и аналитика', '/otzyvy/'],
7    ['Вопросы', '/voprosy/'],
8    ['Чаты', '/chaty/'],
9    ['Тарифы', '/#pricing'],
10    ['Блог', '/blog/'],
11  ];
12
13  /* ---------------- бургер (вне дизайн-рантайма) ---------------- */
14  function buildNav() {
15    if (document.getElementById('blBurger')) return;
16
17    var btn = document.createElement('button');
18    btn.type = 'button';
19    btn.id = 'blBurger';
20    btn.className = 'bl-burger';
21    btn.setAttribute('aria-label', 'Меню');
22    btn.innerHTML = '<span></span><span></span><span></span>';
23
24    var panel = document.createElement('nav');
25    panel.id = 'blPanel';
26    panel.className = 'bl-mnav';
27    LINKS.forEach(function (l) {
28      var a = document.createElement('a');
29      a.href = l[1];
30      a.textContent = l[0];
31      panel.appendChild(a);
32    });
33    var demo = document.createElement('a');
34    demo.href = 'https://t.me/BlaberHELLO_bot';
35    demo.target = '_blank';
36    demo.rel = 'noopener';
37    demo.textContent = 'Демо';
38    demo.className = 'bl-mnav-demo';
39    panel.appendChild(demo);
40    var cta = document.createElement('a');
41    cta.href = 'https://my.blaber.ai/';
42    cta.target = '_blank';
43    cta.rel = 'noopener';
44    cta.textContent = 'Подключить бесплатно';
45    cta.className = 'bl-mnav-cta';
46    panel.appendChild(cta);
47
48    document.body.appendChild(btn);
49    document.body.appendChild(panel);
50
51    btn.addEventListener('click', function (e) {
52      e.preventDefault();
53      e.stopPropagation();
54      var now = Date.now();
55      if (now - lastToggle < 450) return;
56      lastToggle = now;
57      document.documentElement.classList.toggle('bl-open');
58    });
59    panel.addEventListener('click', function (e) {
60      if (e.target.closest('a')) document.documentElement.classList.remove('bl-open');
61    });
62  }
63
64  /* ---------------- мобильные фиксы вёрстки ---------------- */
65  function fixFlexRows() {
66    if (window.innerWidth > 760) return;
67    var vw = window.innerWidth;
68    document.querySelectorAll('div,section').forEach(function (el) {
69      if (el.id === 'blPanel' || el.id === 'blBurger') return;
70      if (el.closest('[style*="animation"]')) return;
71      var cs;
72      try { cs = getComputedStyle(el); } catch (e) { return; }
73      if (cs.display !== 'flex' || cs.flexDirection !== 'row' || cs.flexWrap === 'wrap') return;
74      if (cs.animationName && cs.animationName !== 'none') return;
75      var kids = el.children;
76      for (var i = 0; i < kids.length; i++) {
77        var r = kids[i].getBoundingClientRect();
78        if (r.right > vw + 4 || r.width > vw - 24) { el.classList.add('bl-fixwrap'); break; }
79      }
80    });
81  }
82
83  function freezeSvg() {
84    if (window.innerWidth > 760) return;
85    document.querySelectorAll('svg animate, svg animateTransform').forEach(function (a) { a.remove(); });
86  }
87
88  /* ---------------- типографика ----------------
89     1) знак препинания не начинает строку (склеиваем с предыдущим элементом);
90     2) предлоги/союзы не висят в конце строки (неразрывный пробел после них). */
91  var SHORT_WORDS = ('а,и,но,да,или,либо,что,как,где,кто,чей,бы,ли,же,не,ни,в,во,на,по,за,из,ис,к,ко,с,со,у,о,об,обо,от,ото,до,'
92    + 'для,при,про,над,под,без,через,между,перед,около,после,вместо,кроме,ради,сквозь,среди,благодаря,'
93    + 'это,этот,эта,эти,тот,та,те,мы,вы,он,она,они,я,их,его,её,ваш,ваша,ваши,наш,наша,наши').split(',');
94  var SHORT_RE = new RegExp('(^|[\\s(«"—-])(' + SHORT_WORDS.join('|') + ')\\s+', 'gi');
95  var PUNCT_RE = /^[\s]*[,.;:!?%…»)]/;
96  var SKIP_TAGS = { SCRIPT: 1, STYLE: 1, TEXTAREA: 1, INPUT: 1, CODE: 1, PRE: 1, SVG: 1, NOSCRIPT: 1 };
97
98  function typography(root) {
99    var walker = document.createTreeWalker(root || document.body, NodeFilter.SHOW_TEXT, {
100      acceptNode: function (n) {
101        if (!n.nodeValue || !n.nodeValue.trim()) return NodeFilter.FILTER_REJECT;
102        var p = n.parentNode;
103        while (p && p !== document.body) {
104          if (SKIP_TAGS[p.nodeName] || p.classList && p.classList.contains('bl-typo-done')) return NodeFilter.FILTER_REJECT;
105          p = p.parentNode;
106        }
107        return NodeFilter.FILTER_ACCEPT;
108      }
109    });
110    var nodes = [], n;
111    while ((n = walker.nextNode())) nodes.push(n);
112
113    nodes.forEach(function (node) {
114      // 1) знак препинания сразу после элемента — приклеиваем к нему
115      var prev = node.previousSibling;
116      if (prev && prev.nodeType === 1 && PUNCT_RE.test(node.nodeValue)) {
117        var m = node.nodeValue.match(/^[\s]*([,.;:!?%…»)]+)/);
118        if (m && !prev.classList.contains('bl-glued')) {
119          var punct = m[1];
120          node.nodeValue = node.nodeValue.slice(m[0].length);
121          var wrap = document.createElement('span');
122          wrap.className = 'bl-glued';
123          wrap.style.whiteSpace = 'nowrap';
124          prev.parentNode.insertBefore(wrap, prev);
125          wrap.appendChild(prev);
126          wrap.appendChild(document.createTextNode(punct));
127        }
128      }
129      // 2) короткие слова липнут к следующему
130      var v = node.nodeValue;
131      var nv = v.replace(SHORT_RE, function (all, before, word) { return before + word + ' '; });
132      if (nv !== v) node.nodeValue = nv;
133    });
134  }
135
136  /* ---------------- якоря (#pricing и прочие) ---------------- */
137  function scrollToHash(hash, smooth) {
138    if (!hash || hash.length < 2) return false;
139    var el;
140    try { el = document.querySelector(hash); } catch (e) { return false; }
141    if (!el) return false;
142    var top = el.getBoundingClientRect().top + window.pageYOffset - 90;
143    // далеко — прыгаем сразу (плавная прокрутка на 15 000px длится вечность)
144    var far = Math.abs(top - window.pageYOffset) > 2500;
145    window.scrollTo({ top: top, behavior: (smooth && !far) ? 'smooth' : 'auto' });
146    return true;
147  }
148
149  function initAnchors() {
150    // клик по ссылке с якорем на этой же странице — плавный скролл без перезагрузки
151    document.addEventListener('click', function (e) {
152      var a = e.target.closest && e.target.closest('a[href*="#"]');
153      if (!a) return;
154      var href = a.getAttribute('href') || '';
155      var parts = href.split('#');
156      if (parts.length < 2 || !parts[1]) return;
157      var path = parts[0] || location.pathname;
158      if (path !== location.pathname && path !== '/' + location.pathname.replace(/^\//, '')) {
159        if (!(path === '/' && location.pathname === '/')) return; // другая страница — обычный переход
160      }
161      if (scrollToHash('#' + parts[1], true)) {
162        e.preventDefault();
163        history.replaceState(null, '', '#' + parts[1]);
164        document.documentElement.classList.remove('bl-open');
165      }
166    }, true);
167
168    // пришли по ссылке с якорем: дизайн-рантайм дорисовывает контент — ждём цель
169    if (location.hash) {
170      var tries = 0;
171      var t = setInterval(function () {
172        tries += 1;
173        if (scrollToHash(location.hash, false) || tries > 30) clearInterval(t);
174      }, 200);
175    }
176  }
177
178  var debounceT = null;
179  function runAll() { buildNav(); fixFlexRows(); freezeSvg(); typography(); }
180  function debounced() { clearTimeout(debounceT); debounceT = setTimeout(runAll, 350); }
181
182  if (document.readyState === 'loading') {
183    document.addEventListener('DOMContentLoaded', function () { runAll(); initAnchors(); });
184  } else {
185    runAll(); initAnchors();
186  }
187  window.addEventListener('load', debounced);
188  window.addEventListener('resize', debounced);
189  try { new MutationObserver(debounced).observe(document.body, { childList: true, subtree: true }); } catch (e) {}
190})();

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.