PageSourceSearch

https://vw-autobat.ru/site/templates/scripts/additional.js?v=20260828a

js vw-autobat.ru collected 2026-09-26 10:42:35 UTC 25,498 bytes, 530 lines download raw bytes

1// Cookie notice
2function getCookie(name) {
3  let matches = document.cookie.match(
4    new RegExp(
5      "(?:^|; )" +
6        name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") +
7        "=([^;]*)",
8    ),
9  );
10  return matches ? decodeURIComponent(matches[1]) : undefined;
11}
12let cookiecook = getCookie("cookiecook"),
13  cookiewin = document.getElementsByClassName("cookie_notice")[0];
14if (cookiecook != "no" && cookiewin) {
15  cookiewin.style.display = "block";
16  document
17    .getElementById("cookie_close")
18    .addEventListener("click", function () {
19      cookiewin.style.display = "none";
20      let date = new Date();
21      date.setDate(date.getDate() + 180);
22      document.cookie = "cookiecook=no; path=/; expires=" + date.toUTCString();
23    });
24}
25
26// UTM tracking — save params from URL to sessionStorage
27(function() {
28  var params = new URLSearchParams(window.location.search);
29  ["utm_source", "utm_campaign", "utm_medium", "utm_content", "utm_term"].forEach(function(p) {
30    if (params.get(p)) sessionStorage.setItem(p, params.get(p));
31  });
32})();
33
34/* Snap-scroll nav helper — finds next/prev child snap point */
35function snapNavScroll(el,dir){
36  var items=el.children;var cur=el.scrollLeft;var best=cur;
37  for(var i=0;i<items.length;i++){
38    var off=items[i].offsetLeft-el.offsetLeft;
39    if(dir>0&&off>cur+2){best=off;break;}
40    if(dir<0&&off<cur-2){best=off;}
41  }
42  el.scrollTo({left:best,behavior:'smooth'});
43}
44
45/* ============================================================
46   ADDITIONAL.JS — per-project site interactions, one bundle.
47   The kit ships empty. Build interactions from scratch per project:
48   append each block as a self-contained IIFE that guards on element presence,
49   so loading this file on every page stays safe regardless of which blocks render.
50
51   Template:
52   (function(){
53     'use strict';
54     var nodes = document.querySelectorAll('[data-myblock]');
55     if(!nodes.length) return;            // guard: do nothing if block absent
56     // ... block behaviour ...
57   })();
58   ============================================================ */
59
60/* ████ MOTION · reveal (shared) ████ */
61(function(){
62  'use strict';
63  var els = document.querySelectorAll('[data-reveal]');
64  if(!els.length) return;
65  var reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
66  if(reduce || !('IntersectionObserver' in window)){
67    els.forEach(function(e){ e.classList.add('is-in'); });
68    return;
69  }
70  var io = new IntersectionObserver(function(ents){
71    ents.forEach(function(en){ if(en.isIntersecting){ en.target.classList.add('is-in'); io.unobserve(en.target); } });
72  }, {threshold:0.12, rootMargin:'0px 0px -8% 0px'});
73  els.forEach(function(e){ io.observe(e); });
74  // safety: in throttled/unpainted/hidden iframes IO never fires AND CSS transitions
75  // are frozen — above-fold reveals fire near-instantly when painting, so if NONE have
76  // after a budget, hard-finalize visuals (no transition) + finalize counters.
77  setTimeout(function(){
78    if(document.querySelectorAll('[data-reveal].is-in').length === 0){
79      els.forEach(function(e){ e.style.transition='none'; e.classList.add('is-in'); e.style.opacity='1'; e.style.transform='none'; });
80      document.querySelectorAll('[data-count]').forEach(function(n){ n.textContent = parseFloat(n.getAttribute('data-count')).toLocaleString('ru-RU'); });
81    }
82  }, 1400);
83})();
84
85/* ████ _header · sticky shadow + search + drawer ████ */
86(function(){
87  'use strict';
88  var hdr = document.querySelector('.hdr');
89  if(hdr){
90    var sy = function(){ return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; };
91    // fixed header → reserve its height so content isn't hidden underneath
92    var setPad = function(){ var h = hdr.offsetHeight; document.body.style.paddingTop = h + 'px'; document.documentElement.style.setProperty('--hdr-h', h + 'px'); };
93    setPad();
94    window.addEventListener('resize', setPad);
95    if(window.requestAnimationFrame) requestAnimationFrame(setPad);
96    var lastY = sy(), ticking = false;
97    var onScrollDir = function(){
98      var y = sy();
99      if(Math.abs(y - lastY) > 6){                                     // ignore jitter
100        if(y > lastY && y > 140){ hdr.classList.add('hdr--up'); }      // scrolling down
101        else { hdr.classList.remove('hdr--up'); }                      // scrolling up
102        lastY = y;
103      }
104      ticking = false;
105    };
106    window.addEventListener('scroll', function(){
107      if(!ticking){ requestAnimationFrame(onScrollDir); ticking = true; }
108    }, {passive:true});
109  }
110  var ovl = document.querySelector('[data-search]');
111  document.querySelectorAll('[data-search-open]').forEach(function(b){
112    b.addEventListener('click', function(){ if(ovl){ ovl.classList.add('is-open'); var f=ovl.querySelector('input'); if(f) setTimeout(function(){f.focus();},120); } });
113  });
114  document.querySelectorAll('[data-search-close]').forEach(function(b){
115    b.addEventListener('click', function(){ if(ovl) ovl.classList.remove('is-open'); });
116  });
117  var drw = document.querySelector('[data-drawer]');
118  var scrim = document.querySelector('[data-mega-scrim]');
119  var toggles = document.querySelectorAll('[data-drawer-toggle]');
120  var setMega = function(open){
121    if(drw) drw.classList.toggle('is-open', open);
122    if(scrim) scrim.classList.toggle('is-open', open);
123    toggles.forEach(function(b){ b.classList.toggle('is-x', open); });
124  };
125  toggles.forEach(function(b){
126    b.addEventListener('click', function(){ setMega(!(drw && drw.classList.contains('is-open'))); });
127  });
128  if(scrim) scrim.addEventListener('click', function(){ setMega(false); });
129  // offers page filter (бренд, multi-select toggle; направления разведены по группам)
130  (function(){
131    var root = document.querySelector('[data-offers-filter]');
132    if(!root) return;
133    var activeBrands = new Set();
134    var activeDirs = new Set();
135    var cards = root.querySelectorAll('.ocard');
136    var empty = root.querySelector('.offers-empty');
137    var groups = root.querySelectorAll('[data-of-group]');
138    function apply(){
139      var shown = 0;
140      cards.forEach(function(c){
141        var b = c.getAttribute('data-brand'), d = c.getAttribute('data-dir');
142        var brandOk = activeBrands.size === 0 || activeBrands.has(b);
143        var dirOk = activeDirs.size === 0 || activeDirs.has(d);
144        var ok = brandOk && dirOk;
145        if(ok){
146          shown++;
147          if(c.classList.contains('is-gone')){
148            c.classList.remove('is-gone');
149            requestAnimationFrame(function(){ requestAnimationFrame(function(){ c.classList.remove('is-fade'); }); });
150          } else {
151            c.classList.remove('is-fade');
152          }
153        } else {
154          if(!c.classList.contains('is-gone')){
155            c.classList.add('is-fade');
156            setTimeout(function(){ if(c.classList.contains('is-fade')) c.classList.add('is-gone'); }, 350);
157          }
158        }
159      });
160      // группа целиком уходит, если в ней не осталось видимых карточек
161      groups.forEach(function(g){
162        var vis = 0;
163        g.querySelectorAll('.ocard').forEach(function(c){
164          var b = c.getAttribute('data-brand');
165          if(activeBrands.size === 0 || activeBrands.has(b)) vis++;
166        });
167        g.classList.toggle('is-gone', vis === 0);
168        var cnt = g.querySelector('.ofgroup__count');
169        if(cnt) cnt.textContent = vis;
170      });
171      if(empty) empty.classList.toggle('is-on', shown===0);
172    }
173    root.querySelectorAll('[data-of-brand]').forEach(function(b){
174      b.addEventListener('click', function(){
175        var val = b.getAttribute('data-of-brand');
176        if(activeBrands.has(val)){ activeBrands.delete(val); b.classList.remove('is-on'); }
177        else { activeBrands.add(val); b.classList.add('is-on'); }
178        apply();
179      });
180    });
181    root.querySelectorAll('[data-of-dir]').forEach(function(b){
182      b.addEventListener('click', function(){
183        var val = b.getAttribute('data-of-dir');
184        if(activeDirs.has(val)){ activeDirs.delete(val); b.classList.remove('is-on'); }
185        else { activeDirs.add(val); b.classList.add('is-on'); }
186        apply();
187      });
188    });
189    apply();
190  })();
191  // contact dealer-center switch (data-driven)
192  (function(){
193    var tabs = document.querySelector('[data-dc-tabs]');
194    if(!tabs) return;
195    var DC = {
196      kgm:   {title:'Дилерский центр «Автобат» · KGM', addr:'г. Владимир, ул. Лакина, 4 Г', tel:'+74922474670', phone:'+7 (4922) 47-46-70', email:'[email protected]', hours:'Пн–Вс: 09:00–20:00', badge:'ул. Лакина, 4 Г', px:'50%', py:'50%', gx:'0px', gy:'0px'},
197      bestune:{title:'Дилерский центр «Автобат» · BESTUNE', addr:'г. Владимир, Суздальский пр-т, 16', tel:'+74922112233', phone:'+7 (4922) 11-22-33', email:'[email protected]', hours:'Пн–Пт: 09:00–19:00 · Сб–В
197с: 10:00–17:00', badge:'Суздальский пр-т, 16', px:'32%', py:'62%', gx:'-46px', gy:'-23px'},
198      swm:   {title:'Дилерский центр «Автобат» · SWM', addr:'г. Владимир, ул. Лакина, 4 Г', tel:'+74922474670', phone:'+7 (4922) 47-46-70', email:'[email protected]', hours:'Пн–Вс: 09:00–20:00', badge:'ул. Лакина, 4 Г', px:'58%', py:'44%', gx:'23px', gy:'0px'},
199      vgv:   {title:'Дилерский центр «Автобат» · VGV', addr:'г. Владимир, ул. Лакина, 4 Г', tel:'+74922474670', phone:'+7 (4922) 47-46-70', email:'[email protected]', hours:'Пн–Вс: 09:00–20:00', badge:'ул. Лакина, 4 Г', px:'46%', py:'56%', gx:'0px', gy:'-23px'},
200      vw:    {title:'Сервис Volkswagen · Автобат', addr:'г. Владимир, ул. Лакина, 4 Г', tel:'+74922474670', phone:'+7 (4922) 47-46-70', email:'[email protected]', hours:'Пн–Сб: 08:00–20:00', badge:'Сервис VW · ул. Лакина, 4 Г', px:'52%', py:'48%', gx:'0px', gy:'0px'}
201    };
202    var elTitle=document.querySelector('[data-c-title]'),
203        elAddr=document.querySelector('[data-c-addr]'),
204        elPhone=document.querySelector('[data-c-phone]'),
205        elEmail=document.querySelector('[data-c-email]'),
206        elHours=document.querySelector('[data-c-hours]'),
207        elBadge=document.querySelector('[data-c-badge]'),
208        pin=document.querySelector('.cmap__pin'),
209        grid=document.querySelector('.cmap__grid');
210    function apply(k){
211      var d=DC[k]; if(!d) return;
212      var card=document.querySelector('[data-c-card]');
213      var fade=[card,elBadge];
214      fade.forEach(function(el){ if(el) el.classList.add('dc-fade'); });
215      setTimeout(function(){
216        if(elTitle) elTitle.textContent=d.title;
217        if(elAddr) elAddr.textContent=d.addr;
218        if(elPhone){ elPhone.textContent=d.phone; elPhone.setAttribute('href','tel:'+d.tel); }
219        if(elEmail){ elEmail.textContent=d.email; elEmail.setAttribute('href','mailto:'+d.email); }
220        if(elHours) elHours.textContent=d.hours;
221        if(elBadge){ elBadge.textContent=d.badge; elBadge.style.left=d.px; elBadge.style.top=d.py; }
222        if(pin){ pin.style.left=d.px; pin.style.top=d.py; }
223        if(grid){ grid.style.backgroundPosition=d.gx+' '+d.gy; }
224        fade.forEach(function(el){ if(el) el.classList.remove('dc-fade'); });
225      }, 280);
226    }
227    tabs.addEventListener('click', function(e){
228      var chip=e.target.closest('.cpill'); if(!chip) return;
229      tabs.querySelectorAll('.cpill').forEach(function(c){ c.classList.toggle('is-on', c===chip); });
230      apply(chip.getAttribute('data-dc'));
231    });
232  })();
233  // brand filter tabs (Все / Новые / С пробегом)
234  document.querySelectorAll('[data-brandfilter]').forEach(function(sec){
235    var tabs = sec.querySelector('[data-tabs]');
236    var cards = sec.querySelectorAll('.brandcard');
237    var used = sec.querySelector('[data-used]');
238    if(!tabs) return;
239    tabs.addEventListener('click', function(e){
240      var b = e.target.closest('.ptab'); if(!b) return;
241      var f = b.getAttribute('data-filter');
242      cards.forEach(function(c){ c.style.display = (f === 'used') ? 'none' : ''; });
243      if(used) used.style.display = (f === 'new') ? 'none' : '';
244    });
245  });
246  // mobile sheet: collapsible sections (accordion)
247  document.querySelectorAll('.mega__col > h4').forEach(function(h){
248    h.addEventListener('click', function(){ h.parentElement.classList.toggle('is-exp'); });
249  });
250  if(drw) drw.querySelectorAll('a,[data-drawer-close]').forEach(function(a){
251    a.addEventListener('click', function(){ setMega(false); });
252  });
253  document.addEventListener('keydown', function(e){
254    if(e.key==='Escape'){ if(ovl) ovl.classList.remove('is-open'); setMega(false); }
255  });
256})();
257
258/* ████ block_faq · accordion ████ */
259(function(){
260  'use strict';
261  var items = document.querySelectorAll('.faq__item');
262  if(!items.length) return;
263  items.forEach(function(item){
264    var q = item.querySelector('.faq__q');
265    var a = item.querySelector('.faq__a');
266    if(!q || !a) return;
267    q.addEventListener('click', function(){
268      var open = item.classList.contains('is-open');
269      var scope = item.closest('.faq') || document;
270      scope.querySelectorAll('.faq__item.is-open').forEach(function(o){
271        if(o!==item){ o.classList.remove('is-open'); var oa=o.querySelector('.faq__a'); if(oa) oa.style.height='0px'; }
272      });
273      if(open){ item.classList.remove('is-open'); a.style.height='0px'; }
274      else { item.classList.add('is-open'); a.style.height = a.firstElementChild.offsetHeight + 'px'; }
275    });
276  });
277})();
278
279/* ████ block_stats · counters ████ */
280(function(){
281  'use strict';
282  var nums = document.querySelectorAll('[data-count]');
283  if(!nums.length) return;
284  function run(el){
285    var to = parseFloat(el.getAttribute('data-count')), dur = 1500, t0 = null;
286    function step(ts){
287      if(!t0) t0 = ts;
288      var p = Math.min((ts - t0)/dur, 1), e = 1 - Math.pow(1-p,3);
289      el.textContent = Math.round(to*e).toLocaleString('ru-RU');
290      if(p<1) requestAnimationFrame(step);
291    }
292    requestAnimationFrame(step);
293  }
294  if(!('IntersectionObserver' in window)){ nums.forEach(function(n){ n.textContent = parseFloat(n.getAttribute('data-count')).toLocaleString('ru-RU'); }); return; }
295  var io = new IntersectionObserver(function(ents){
296    ents.forEach(function(en){ if(en.isIntersecting){ run(en.target); io.unobserve(en.target); } });
297  }, {threshold:0.5});
298  nums.forEach(function(n){ io.observe(n); });
299  // safety: if values never animated (rAF/IO frozen in hidden iframe), finalize
300  setTimeout(function(){
301    var first = document.querySelector('[data-count]');
302    if(first && first.textContent === '0'){
303      nums.forEach(function(n){ n.textContent = parseFloat(n.getAttribute('data-count')).toLocaleString('ru-RU'); });
304    }
305  }, 2600);
306})();
307
308/* ████ block_hero · subtle media parallax ████ */
309(function(){
310  'use strict';
311  var els = document.querySelectorAll('[data-parallax]');
312  if(!els.length || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
313  window.addEventListener('scroll', function(){
314    var y = window.scrollY;
315    els.forEach(function(el){ var s = parseFloat(el.getAttribute('data-parallax'))||0.06; el.style.transform = 'translate3d(0,'+(y*s)+'px,0)'; });
316  }, {passive:true});
317})();
318
319/* ████ pill tabs (visual toggle) ████ */
320(function(){
321  'use strict';
322  var groups = document.querySelectorAll('[data-tabs]');
323  if(!groups.length) return;
324  groups.forEach(function(g){
325    g.addEventListener('click', function(e){
326      var t = e.target.closest('.ptab');
327      if(!t || !g.contains(t)) return;
328      g.querySelectorAll('.ptab').forEach(function(b){ b.classList.toggle('is-on', b===t); });
329    });
330  });
331})();
332
333/* ████ type carousel ████ */
334(function(){
335  'use strict';
336  var scrollers = document.querySelectorAll('[data-tscroll]');
337  if(!scrollers.length) return;
338  scrollers.forEach(function(track){
339    var wrap = track.closest('[data-tscroll-wrap]') || track.parentElement;
340    var step = function(){ var c = track.querySelector('*'); return c ? c.getBoundingClientRect().width + 16 : 240; };
341    var prev = wrap.querySelector('[data-tscroll-prev]');
342    var next = wrap.querySelector('[data-tscroll-next]');
343    function snapNav(dir){
344      var items = track.children;
345      var tRect = track.getBoundingClientRect();
346      var cur = track.scrollLeft;
347      var best = cur;
348      for(var i=0;i<items.length;i++){
349        var off = items[i].offsetLeft - track.offsetLeft;
350        if(dir>0 && off > cur+2){ best=off; break; }
351        if(dir<0 && off < cur-2){ best=off; }
352      }
353      track.scrollTo({left:best, behavior:'smooth'});
354    }
355    if(prev) prev.addEventListener('click', function(){ snapNav(-1); });
356    if(next) next.addEventListener('click', function(){ snapNav(1); });
357    // Mouse drag to scroll
358    var down=false,sx=0,sl=0,dragged=false;
359    track.addEventListener("mousedown",function(e){down=true;dragged=false;sx=e.pageX;sl=track.scrollLeft;track.style.cursor="grabbing";});
360    track.addEventListener("mousemove",function(e){if(!down)return;e.preventDefault();var dx=e.pageX-sx;if(Math.abs(dx)>5)dragged=true;track.scrollLeft=sl-dx;});
361    track.addEventListener("mouseup",function(){down=false;track.style.cursor="";});
362    track.addEventListener("mouseleave",function(){down=false;track.style.cursor="";});
363    track.addEventListener("click",function(e){if(dragged){e.preventDefault();e.stopPropagation();dragged=false;}},true);
364
365  });
366})();
367
368/* ████ hero variant switcher (preview) ████ */
369(function(){
370  'use strict';
371  var sw = document.querySelector('[data-vswitch]');
372  if(!sw) return;
373  var heroes = document.querySelectorAll('[data-hero-variant]');
374  function show(n){
375    heroes.forEach(function(h){ h.hidden = (h.getAttribute('data-hero-variant') !== String(n)); });
376    sw.querySelectorAll('button').forEach(function(b){ b.classList.toggle('is-on', b.getAttribute('data-hero')===String(n)); });
377    var active = document.querySelector('[data-hero-variant="'+n+'"]');
378    if(active) active.querySelectorAll('[data-reveal]').forEach(function(e){ e.style.transition='none'; e.classList.add('is-in'); e.style.opacity='1'; e.style.transform='none'; });
379    try{ localStorage.setItem('autobat_hero', n); }catch(e){}
380  }
381  sw.addEventListener('click', function(e){
382    var b = e.target.closest('button[data-hero]'); if(!b) return;
383    show(b.getAttribute('data-hero'));
384    window.scrollTo({top:0,behavior:'smooth'});
385  });
386  var saved = '1'; try{ saved = localStorage.getItem('autobat_hero') || '1'; }catch(e){}
387  show(saved);
388})();
389
390/* ████ banner slider (V1/V2) — supports multiple ████ */
391(function(){
392  'use strict';
393  var roots = document.querySelectorAll('[data-slider]');
394  if(!roots.length) return;
395  roots.forEach(function(root){
396    var track = root.querySelector('.slides');
397    var slides = root.querySelectorAll('.slide');
398    var dotsBox = root.querySelector('[data-dots]');
399    var i = 0, timer = null, n = slides.length;
400    if(dotsBox){
401      for(var d=0; d<n; d++){
402        var dot = document.createElement('button');
403        dot.className = 'sl-dot' + (d===0?' is-on':'');
404        dot.setAttribute('aria-label','Слайд '+(d+1));
405        (function(idx){ dot.addEventListener('click', function(){ go(idx); reset(); }); })(d);
406        dotsBox.appendChild(dot);
407      }
408    }
409    var dots = dotsBox ? dotsBox.querySelectorAll('.sl-dot') : [];
410    function go(idx){ i = (idx+n)%n; track.style.transform = 'translateX(-'+(i*100)+'%)'; dots.forEach(function(x,k){ x.classList.toggle('is-on',k===i); }); }
411    function next(){ go(i+1); } function prev(){ go(i-1); }
412    function play(){ clearInterval(timer); timer = setInterval(next, 7500); }
413    function reset(){ play(); }
414    var nb = root.querySelector('[data-next]'), pb = root.querySelector('[data-prev]');
415    if(nb) nb.addEventListener('click', function(){ next(); reset(); });
416    if(pb) pb.addEventListener('click', function(){ prev(); reset(); });
417    root.addEventListener('mouseenter', function(){ clearInterval(timer); });
418    root.addEventListener('mouseleave', play);
419    document.addEventListener('visibilitychange', function(){ if(document.hidden){ clearInterval(timer); } else { play(); } });
420    var touchX0 = null;
421    track.addEventListener('touchstart', function(e){ touchX0 = e.touches[0].clientX; }, {passive:true});
422    track.addEventListener('touchend', function(e){
423      if(touchX0 === null) return;
424      var dx = e.changedTouches[0].clientX - touchX0;
425      touchX0 = null;
426      if(Math.abs(dx) < 40) return;
427      if(dx < 0) next(); else prev();
428      reset();
429    });
430    play();
431  });
432})();
433
434/* ████ credit calculator (hero CTA) ████ */
435(function(){
436  'use strict';
437  var box = document.querySelector('[data-calc]');
438  if(!box) return;
439  var range = box.querySelector('.crange');
440  var payEl = box.querySelector('[data-calc-pay]');
441  var bubble = box.querySelector('[data-calc-years]');
442  var amount = parseFloat(box.getAttribute('data-amount')) || 2000000;
443  var rate = (parseFloat(box.getAttribute('data-rate')) || 12) / 100 / 12;
444  function plural(y){ var m=y%10,d=y%100; if(m===1&&d!==11) return 'год'; if(m>=2&&m<=4&&(d<10||d>=20)) return 'года'; return 'лет'; }
445  function fmt(v){ return Math.round(v).toLocaleString('ru-RU'); }
446  function update(){
447    var years = parseInt(range.value,10);
448    var nM = years*12;
449    var pay = amount * (rate*Math.pow(1+rate,nM)) / (Math.pow(1+rate,nM)-1);
450    if(payEl) payEl.textContent = 'От ' + fmt(pay) + ' ₽/мес';
451    if(bubble) bubble.textContent = years + ' ' + plural(years);
452    var pct = (years - range.min) / (range.max - range.min) * 100;
453    range.style.background = 'linear-gradient(90deg, var(--color-accent) '+pct+'%, hsla(0,0%,100%,.2) '+pct+'%)';
454  }
455  range.addEventListener('input', update);
456  update();
457})();
458
459;(function () {
460  if (!('ontouchstart' in window)) return;
461  var sx = 0, sy = 0, st = 0, moved = false, tracking = false, pending = null, timer = null;
462  function clear() { if (timer) { clearTimeout(timer); timer = null; } pending = null; }
463  function onStart(e) {
464    if (e.touches && e.touches.length > 1) { tracking = false; return; }
465    var t = e.changedTouches[0];
466    sx = t.clientX; sy = t.clientY; st = e.timeStamp; moved = false; tracking = true;
467  }
468  function onMove(e) {
469    if (!tracking) return;
470    var t = e.changedTouches[0];
471    if (Math.abs(t.clientX - sx) > 8 || Math.abs(t.clientY - sy) > 8) moved = true;
472  }
473  function onEnd(e) {
474    if (!tracking) return;
475    tracking = false;
476    if (moved || e.timeStamp - st > 700) return;
477    if (!e.target || !e.target.closest) return;
478    var link = e.target.closest('a.carousel__item, .carousel__item a[href]');
479    if (!link || !link.closest('.js-carousel')) return;
480    var href = link.getAttribute('href');
481    if (!href || href.charAt(0) === '#') return;
482    clear();
483    pending = { href: href, blank: link.getAttribute('target') === '_blank' };
484    timer = setTimeout(function () {
485      var p = pending; clear();
486      if (!p) return;
487      if (p.blank) window.open(p.href, '_blank'); else window.location.href = p.href;
488    }, 120);
489  }
490  function onClick() { clear(); }
491  document.addEventListener('touchstart', onStart, { passive: true });
492  document.addEventListener('touchmove', onMove, { passive: true });
493  document.addEventListener('touchend', onEnd, { passive: true });
494  document.addEventListener('click', onClick, true);
495})();
496
497// Блок «Видео 9:16»: превью крутится без звука, когда карточка в зоне видимости;
498// клик открывает общую модалку #modal-video (компонент ModalVideo из main.js).
499(function () {
500  var cards = document.querySelectorAll('.vvid__card .vvid__media');
501  if (!cards.length) return;
502  var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
503  // превью крутим только на десктопе: на мобильных это лишний трафик, там первый кадр + кнопка play
504  var conn = navigator.connection || {};
505  var canPreview = window.matchMedia('(hover: hover) and (pointer: fine)').matches && !conn.saveData;
506
507  if (!reduce && canPreview && 'IntersectionObserver' in window) {
508    var io = new IntersectionObserver(function (entries) {
509      entries.forEach(function (e) {
510        var v = e.target;
511        if (e.isIntersecting) { var p = v.play(); if (p && p.catch) p.catch(function () {}); }
512        else v.pause();
513      });
514    }, { threshold: 0.4 });
515    cards.forEach(function (v) { io.observe(v); });
516  }
517
518  // в модалке видео стартует само — открытие уже было жестом пользователя
519  var modal = document.getElementById('modal-video');
520  if (modal) {
521    var media = modal.querySelector('.js-modal-video__media');
522    if (media) {
523      media.addEventListener('loadedmetadata', function () {
524        var p = media.play();
525        if (p && p.catch) p.catch(function () {});
526      });
527      modal.addEventListener('modalIsClose', function () { media.pause(); });
528    }
529  }
530})();

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.