PageSourceSearch

https://gonzoproxy.com/js/main.js?v=8565b660

js gonzoproxy.com collected 2026-09-29 15:32:52 UTC 16,122 bytes, 370 lines download raw bytes

1/* GonzoProxy landing — vanilla-рантайм вместо jQuery + webflow.js.
2 * Компоненты: Webflow.push-шим, w-nav (бургер), w-dropdown, w-tabs,
3 * формы (Webflow Forms API, на localhost — имитация успеха), маркизы,
4 * days-select, плавный скролл к якорям, плавающая кнопка Telegram. */
5(function () {
6  'use strict';
7
8  var IS_LOCAL = /^(localhost|127\.|0\.0\.0\.0|192\.168\.)/.test(location.hostname) || location.hostname === '';
9  var SITE_ID = '67b33c32bd5b234beac4a108';
10  var MEDIUM = window.matchMedia('(max-width: 991px)');
11
12  function ready(fn) {
13    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn);
14    else fn();
15  }
16
17  /* ---------- Webflow.push шим (инлайн-скрипты сайта пушат колбэки) ---------- */
18  var pushed = Array.isArray(window.Webflow) ? window.Webflow.slice() : [];
19  window.Webflow = {
20    push: function (fn) { ready(fn); },
21    destroy: function () {}, ready: function () {}, require: function () { return null; }
22  };
23
24  /* ---------- w-nav: мобильный бургер ---------- */
25  function initNav() {
26    document.querySelectorAll('.w-nav').forEach(function (nav) {
27      var button = nav.querySelector('.w-nav-button');
28      var menu = nav.querySelector('.w-nav-menu');
29      if (!button || !menu) return;
30      var overlay = document.createElement('div');
31      overlay.className = 'w-nav-overlay';
32      overlay.setAttribute('data-wf-ignore', '');
33      overlay.style.display = 'none';
34      nav.appendChild(overlay);
35      var home = { parent: menu.parentNode, next: menu.nextSibling };
36      var open = false;
37
38      function setOpen(state) {
39        if (state === open) return;
40        open = state;
41        if (open) {
42          overlay.appendChild(menu);
43          menu.setAttribute('data-nav-menu-open', '');
44          overlay.style.display = 'block';
45          button.classList.add('w--open');
46          button.setAttribute('aria-expanded', 'true');
47        } else {
48          menu.removeAttribute('data-nav-menu-open');
49          home.parent.insertBefore(menu, home.next);
50          overlay.style.display = 'none';
51          button.classList.remove('w--open');
52          button.setAttribute('aria-expanded', 'false');
53        }
54      }
55
56      button.setAttribute('role', 'button');
57      button.setAttribute('aria-expanded', 'false');
58      button.addEventListener('click', function () { setOpen(!open); });
59      document.addEventListener('click', function (e) {
60        if (open && !nav.contains(e.target)) setOpen(false);
61      });
62      document.addEventListener('keydown', function (e) {
63        if (e.key === 'Escape') setOpen(false);
64      });
65      MEDIUM.addEventListener('change', function (e) {
66        if (!e.matches) setOpen(false);
67      });
68      nav._navClose = function () { setOpen(false); };
69    });
70  }
71
72  /* ---------- w-dropdown: меню, язык, FAQ и гео-аккордеоны ---------- */
73  function initDropdowns() {
74    var dropdowns = Array.prototype.slice.call(document.querySelectorAll('.w-dropdown'));
75    // live (webflow.js) вешает w--open только на toggle и list, корень не трогает
76    function isOpen(dd) {
77      var t = dd.querySelector('.w-dropdown-toggle');
78      return !!(t && t.classList.contains('w--open'));
79    }
80    function close(dd) {
81      if (!isOpen(dd)) return;
82      var t = dd.querySelector('.w-dropdown-toggle');
83      var l = dd.querySelector('.w-dropdown-list');
84      if (t) { t.classList.remove('w--open'); t.setAttribute('aria-expanded', 'false'); }
85      if (l) l.classList.remove('w--open');
86      dd.style.zIndex = '';
87      if (window.GonzoIX2) window.GonzoIX2.notify('DROPDOWN_CLOSE', dd);
88    }
89    function openDd(dd) {
90      var t = dd.querySelector('.w-dropdown-toggle');
91      var l = dd.querySelector('.w-dropdown-list');
92      if (t) { t.classList.add('w--open'); t.setAttribute('aria-expanded', 'true'); }
93      if (l) l.classList.add('w--open');
94      dd.style.zIndex = '901'; // как webflow.js на live
95      if (window.GonzoIX2) window.GonzoIX2.notify('DROPDOWN_OPEN', dd);
96    }
97    dropdowns.forEach(function (dd) {
98      var toggle = dd.querySelector('.w-dropdown-toggle');
99      var list = dd.querySelector('.w-dropdown-list');
100      if (!toggle || !list) return;
101      var hover = dd.getAttribute('data-hover') === 'true';
102      toggle.setAttribute('aria-expanded', 'false');
103      toggle.setAttribute('tabindex', '0');
104
105      toggle.addEventListener('click', function () {
106        if (isOpen(dd)) close(dd); else openDd(dd);
107      });
108      toggle.addEventListener('keydown', function (e) {
109        if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle.click(); }
110      });
111      if (hover) {
112        dd.addEventListener('mouseenter', function () {
113          if (!MEDIUM.matches) openDd(dd);
114        });
115        dd.addEventListener('mouseleave', function () {
116          if (!MEDIUM.matches) close(dd);
117        });
118      }
119    });
120    document.addEventListener('click', function (e) {
121      dropdowns.forEach(function (dd) {
122        if (isOpen(dd) && !dd.contains(e.target)) close(dd);
123      });
124    });
125    document.addEventListener('keydown', function (e) {
126      if (e.key === 'Escape') dropdowns.forEach(close);
127    });
128  }
129
130  /* ---------- w-tabs ---------- */
131  function initTabs() {
132    document.querySelectorAll('.w-tabs').forEach(function (tabs) {
133      var links = tabs.querySelectorAll('.w-tab-link');
134      var panes = tabs.querySelectorAll(':scope .w-tab-pane');
135      // live (webflow.js): fade-out текущей панели (data-duration-out, деф. 100мс),
136      // затем свап и fade-in новой (data-duration-in, деф. 300мс)
137      var durIn = parseInt(tabs.getAttribute('data-duration-in') || '300', 10);
138      var durOut = parseInt(tabs.getAttribute('data-duration-out') || '100', 10);
139      links.forEach(function (link) {
140        link.addEventListener('click', function (e) {
141          e.preventDefault();
142          if (link.classList.contains('w--current')) return;
143          var name = link.getAttribute('data-w-tab');
144          var prev = null;
145          links.forEach(function (l) {
146            if (l.classList.contains('w--current')) prev = l;
147            var cur = l === link;
148            l.classList.toggle('w--current', cur);
149            l.setAttribute('aria-selected', cur ? 'true' : 'false');
150          });
151          if (window.GonzoIX2) {
152            if (prev) window.GonzoIX2.notify('TAB_INACTIVE', prev);
153            window.GonzoIX2.notify('TAB_ACTIVE', link);
154          }
155          var current = null;
156          panes.forEach(function (p) {
157            if (p.classList.contains('w--tab-active')) current = p;
158          });
159          function fadeInNew() {
160            panes.forEach(function (p) {
161              var active = p.getAttribute('data-w-tab') === name;
162              if (active) {
163                p.style.opacity = '0';
164                p.classList.add('w--tab-active');
165                requestAnimationFrame(function () {
166                  p.style.transition = 'opacity ' + durIn + 'ms ease';
167                  p.style.opacity = '1';
168                  setTimeout(function () { p.style.transition = ''; p.style.opacity = ''; }, durIn + 30);
169                });
170              } else {
171                p.classList.remove('w--tab-active');
172                p.style.opacity = '';
173                p.style.transition = '';
174              }
175            });
176          }
177          if (current && durOut > 0) {
178            current.style.transition = 'opacity ' + durOut + 'ms ease';
179            current.style.opacity = '0';
180            setTimeout(fadeInNew, durOut);
181          } else {
182            fadeInNew();
183          }
184        });
185      });
186    });
187  }
188
189  /* ---------- Формы: Webflow Forms API; на localhost — имитация успеха ---------- */
190  function initForms() {
191    document.querySelectorAll('.w-form form').forEach(function (form) {
192      form.addEventListener('submit', function (e) {
193        e.preventDefault();
194        var wrap = form.closest('.w-form');
195        var done = wrap.querySelector('.w-form-done');
196        var fail = wrap.querySelector('.w-form-fail');
197        function show(ok) {
198          if (ok) {
199            form.style.display = 'none';
200            if (done) done.style.display = 'block';
201            if (fail) fail.style.display = 'none';
202          } else if (fail) {
203            fail.style.display = 'block';
204          }
205        }
206        if (IS_LOCAL) { show(true); return; }
207        var params = new URLSearchParams();
208        new FormData(form).forEach(function (v, k) { params.append('fields[' + k + ']', v); });
209        params.append('name', form.getAttribute('data-name') || form.name || 'Form');
210        params.append('pageId', form.getAttribute('data-wf-page-id') || '');
211        params.append('elementId', form.getAttribute('data-wf-element-id') || '');
212        params.append('source', location.href);
213        params.append('test', 'false');
214        params.append('dolphin', 'false');
215        fetch('https://webflow.com/api/v1/form/' + SITE_ID, {
216          method: 'POST',
217          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
218          body: params.toString()
219        }).then(function (r) { show(r.ok); })
220          .catch(function () { show(false); });
221      });
222    });
223  }
224
225  /* ---------- Маркизы логотипов (замена jquery.marquee) ----------
226   * Живой сайт: wrapper 100000px, 2 копии float, margin-right 20px
227   * (rem-значения из конфига плагин не парсит -> дефолт 20px).
228   * Клип делает СЕКЦИЯ (overflow-hidden), не .logo-row — не трогаем overflow. */
229  function initMarquee(selector, direction) {
230    document.querySelectorAll(selector).forEach(function (el) {
231      if (el._marqueeDone) return;
232      el._marqueeDone = true;
233      var inner = el.innerHTML;
234      el.innerHTML = '';
235      var track = document.createElement('div');
236      // структура и классы как у jquery.marquee: wrapper 100000px, копии .js-marquee float:left
237      track.className = 'js-marquee-wrapper gp-marquee-track gp-marquee-' + direction;
238      for (var i = 0; i < 2; i++) {
239        var part = document.createElement('div');
240        part.className = 'js-marquee';
241        part.style.marginRight = '20px';
242        part.style.cssFloat = 'left';
243        part.innerHTML = inner;
244        track.appendChild(part);
245      }
246      el.appendChild(track);
247      // как у jquery.marquee: инлайновый transform-базис (важно для растеризации),
248      // duration = ширина_копии / ширина_контейнера * 70.5s, задержка 190мс
249      track.style.transform = 'translateX(0px)';
250      // jquery.marquee меряет ДО загрузки картинок (ширины карточек фиксированы CSS);
251      // повторный замер на window.load занижает скорость — меряем один раз, на rAF
252      var measure = function () {
253        var w = track.children[0].getBoundingClientRect().width + 20;
254        var cw = el.getBoundingClientRect().width;
255        if (!w || !cw) { requestAnimationFrame(measure); return; }
256        track.style.setProperty('--gp-shift', w + 'px');
257        // формула jquery.marquee (сверено с live на 1280/1440):
258        // duration = 70.5s * (копия+margin+контейнер) / (2*контейнер)
259        track.style.animationDuration = (((w + cw) / (2 * cw)) * 70.5).toFixed(3) + 's';
260        track.style.animationDelay = '0.19s';
261      };
262      requestAnimationFrame(measure);
263    });
264  }
265
266  function injectMarqueeCss() {
267    var style = document.createElement('style');
268    style.textContent =
269      '.gp-marquee-track{width:100000px;' +
270      'animation:gp-marquee-left 70.5s linear infinite}' +
271      '.gp-marquee-track::after{content:"";display:table;clear:both}' +
272      '.gp-marquee-right{animation-name:gp-marquee-right}' +
273      '@keyframes gp-marquee-left{from{transform:translateX(0)}to{transform:translateX(calc(-1 * var(--gp-shift,50%)))}}' +
274      '@keyframes gp-marquee-right{from{transform:translateX(calc(-1 * var(--gp-shift,50%)))}to{transform:translateX(0)}}';
275    document.head.appendChild(style);
276  }
277
278  /* ---------- days-select (дословно с живого сайта) ---------- */
279  function initDaysSelect() {
280    var selectElements = document.querySelectorAll('.days-select');
281    selectElements.forEach(function (select) {
282      var targetId = select.getAttribute('data-target');
283      var outputElement = document.getElementById(targetId);
284      if (!outputElement) return;
285      var lastOption = select.options[select.options.length - 1];
286      select.value = lastOption.value;
287      updateOutput(outputElement, lastOption.value);
288      select.addEventListener('change', function () {
289        updateOutput(outputElement, this.value);
290      });
291    });
292    function updateOutput(el, value) {
293      if ('value' in el) el.value = value;
294      else el.textContent = value;
295    }
296  }
297
298  /* ---------- Плавный скролл к якорям (сверено с live: финал = якорь − хедер,
299     длительность = дистанция × 0.25мс, кривая ~inOutCubic) ---------- */
300  function initSmoothScroll() {
301    function easeInOutCubic(t) {
302      return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
303    }
304    document.addEventListener('click', function (e) {
305      var a = e.target.closest('a[href]');
306      if (!a) return;
307      var href = a.getAttribute('href') || '';
308      if (href.indexOf('#') === -1) return;
309      var url;
310      try { url = new URL(href, location.href); } catch (err) { return; }
311      if (url.origin !== location.origin) return;
312      var samePath = url.pathname.replace(/\/$/, '') === location.pathname.replace(/\/$/, '');
313      if (!samePath || !url.hash) return;
314      var target = document.getElementById(url.hash.slice(1));
315      if (!target) return;
316      e.preventDefault();
317      var header = document.querySelector('.header');
318      var offset = header ? header.offsetHeight : 0;
319      var from = window.scrollY;
320      var to = Math.max(0, target.getBoundingClientRect().top + window.scrollY - offset);
321      var dist = to - from;
322      var dur = Math.max(300, Math.abs(dist) * 0.25);
323      var t0 = performance.now();
324      function stepFrame(now) {
325        var p = Math.min(1, (now - t0) / dur);
326        window.scrollTo(0, from + dist * easeInOutCubic(p));
327        if (p < 1) requestAnimationFrame(stepFrame);
328      }
329      requestAnimationFrame(stepFrame);
330      history.replaceState(null, '', url.hash);
331    });
332  }
333
334  /* ---------- Плавающая кнопка Telegram (замена чата SendPulse) ---------- */
335  function initChatWidget() {
336    var s = document.createElement('script');
337    s.async = true;
338    s.src = '/js/tg-fab.js?v=45dc9459';
339    s.setAttribute('data-tg', 'GonzoProxy_bot');
340    document.body.appendChild(s);
341  }
342
343  /* ---------- Cookiebot — только на боевом домене ---------- */
344  function initCookiebot() {
345    if (IS_LOCAL) return;
346    var s = document.createElement('script');
347    s.id = 'Cookiebot';
348    s.src = 'https://consent.cookiebot.com/uc.js';
349    s.setAttribute('data-cbid', 'f8ac1db1-c1c1-477d-a4fc-71a10aab62bb');
350    s.setAttribute('data-blockingmode', 'auto');
351    s.type = 'text/javascript';
352    document.head.appendChild(s);
353  }
354
355  ready(function () {
356    injectMarqueeCss();
357    if (window.GonzoIX2) window.GonzoIX2.init();
358    initNav();
359    initDropdowns();
360    initTabs();
361    initForms();
362    initMarquee('.js-marquee', 'left');
363    initMarquee('.js-marquee2', 'right');
364    initDaysSelect();
365    initSmoothScroll();
366    initChatWidget();
367    initCookiebot();
368    pushed.forEach(function (fn) { try { fn(); } catch (err) { console.error(err); } });
369  });
370})();

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.