1/** 2 * Venbest Homepage v2 â ÑнÑеÑакÑив Ð½Ð¾Ð²Ð¸Ñ ÑекÑÑй. 3 * RouteMap-анÑмаÑÑÑ (ÐÐÐÐÐÐÐ), каÑÑа УкÑаÑни з оÑÑÑами, hero fade-in. 4 * ÐоÑÑовано з React-лендÑÐ½Ð³Ñ (RouteMap.tsx, UkraineMap.tsx, Index.tsx Hero). 5 * ÐогÑка ETA-попапа лиÑаÑÑÑÑÑ Ð² main.js (initAddressCheck / fetchResponseTime). 6 */ 7(function () { 8 'use strict'; 9 10 var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 11 12 /* ============ ROUTEMAP (анÑмаÑÑÑ Ð¼Ð°ÑÑÑÑÑÑ ÐµÐºÑпажÑ) ============ */ 13 var ROUTE_DURATION = 2500; 14 var ROUTE_START_DELAY = 400; 15 var ARRIVAL_SECONDS = 6 * 60; 16 17 function easeInOutCubic(t) { 18 return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; 19 } 20 21 function formatTime(seconds) { 22 var m = Math.floor(seconds / 60); 23 var s = Math.floor(seconds % 60); 24 return m + ':' + String(s).padStart(2, '0'); 25 } 26 27 function initRouteMap(root) { 28 var track = root.querySelector('.vb-routemap__track'); 29 var progress = root.querySelector('.vb-routemap__progress'); 30 var marker = root.querySelector('.vb-routemap__marker'); 31 var area = root.querySelector('.vb-routemap__area'); 32 var timeEl = root.querySelector('[data-vb-route-time]'); 33 var statusEl = root.querySelector('[data-vb-route-status]'); 34 if (!track || !progress) return null; 35 36 var len = track.getTotalLength(); 37 var rafId = null; 38 var timeoutId = null; 39 var started = false; 40 41 progress.style.strokeDasharray = String(len); 42 progress.style.strokeDashoffset = String(len); 43 44 function setStatus(status) { 45 root.setAttribute('data-status', status); 46 if (statusEl) { 47 statusEl.textContent = 48 status === 'arrived' ? (root.getAttribute('data-label-arrived') || 'ÐкÑпаж пÑибÑв') : 49 status === 'driving' ? (root.getAttribute('data-label-driving') || 'ÐкÑпаж в доÑозÑ') : (root.getAttribute('data-label-idle') || 'ÐÑÑкÑÑ ÑÑивоги'); 50 } 51 } 52 53 function setProgress(p) { 54 progress.style.strokeDashoffset = String(len * (1 - p)); 55 if (marker) { 56 var pt = track.getPointAtLength(len * p); 57 var ahead = track.getPointAtLength(Math.min(len * p + 2, len)); 58 var angle = (Math.atan2(ahead.y - pt.y, ahead.x - pt.x) * 180) / Math.PI + 90; 59 marker.setAttribute('transform', 'translate(' + pt.x + ' ' + pt.y + ') rotate(' + angle + ')'); 60 marker.style.opacity = p > 0 && p < 1 ? '1' : '0'; 61 } 62 if (timeEl) timeEl.textContent = formatTime(p * ARRIVAL_SECONDS); 63 } 64 65 function play() { 66 if (rafId) cancelAnimationFrame(rafId); 67 if (timeoutId) clearTimeout(timeoutId); 68 if (area) area.style.opacity = '0.12'; 69 if (reducedMotion) { 70 setProgress(1); 71 setStatus('arrived'); 72 return; 73 } 74 setProgress(0); 75 setStatus('driving'); 76 timeoutId = setTimeout(function () { 77 var t0 = performance.now(); 78 function tick(now) { 79 var p = easeInOutCubic(Math.min((now - t0) / ROUTE_DURATION, 1)); 80 setProgress(p); 81 if (p < 1) { 82 rafId = requestAnimationFrame(tick); 83 } else { 84 setStatus('arrived'); 85 } 86 } 87 rafId = requestAnimationFrame(tick); 88 }, ROUTE_START_DELAY); 89 } 90 91 setProgress(0); 92 setStatus('idle'); 93 94 // ÐеÑÑий запÑÑк â ÑекÑÑÑ Ñ viewport (один Ñаз) 95 if ('IntersectionObserver' in window) { 96 var observer = new IntersectionObserver(function (entries) { 97 if (entries.some(function (e) { return e.isIntersecting; }) && !started) { 98 started = true; 99 play(); 100 observer.disconnect(); 101 } 102 }, { threshold: 0.35 }); 103 observer.observe(root); 104 } else if (!started) { 105 started = true; 106 play(); 107 } 108 109 return { 110 replay: function (address) { 111 started = true; 112 if (address) { 113 var dest = root.querySelector('[data-vb-route-dest]'); 114 if (dest) dest.textContent = address; 115 } 116 play(); 117 }, 118 }; 119 } 120 121 var routeMaps = []; 122 document.querySelectorAll('[data-vb-routemap]').forEach(function (el) { 123 var api = initRouteMap(el); 124 if (api) routeMaps.push(api); 125 }); 126 127 // Реплей пÑи ÑабмÑÑÑ ÑоÑми пеÑевÑÑки адÑеÑи (ÑоÑма з блока address-check) 128 document.querySelectorAll('.address-check__form').forEach(function (form) { 129 form.addEventListener('submit', function () { 130 var input = form.querySelector('.address-check__input'); 131 var address = input && input.value ? input.value.trim() : ''; 132 routeMaps.forEach(function (rm) { rm.replay(address); }); 133 }); 134 }); 135 136 /* ============ ÐÐРТРУÐÐ ÐÐÐÐ + ÐÐÐÐÐЬ ÐФÐСÐÐ ============ */ 137 function initCoverageMap(section) { 138 var dataEl = section.querySelector('[data-vb-map-data]'); 139 var panel = section.querySelector('[data-vb-map-panel]'); 140 var svg = section.querySelector('.vb-map__svg'); 141 if (!dataEl || !panel || !svg) return; 142 143 var data; 144 try { 145 data = JSON.parse(dataEl.textContent); 146 } catch (e) { 147 return; 148 } 149 var names = data.names || {};
150 var offices = data.offices || {}; 151 var activeCity = section.getAttribute('data-default-city') || 'kiyv'; 152 var officeIdx = 0; 153 154 function esc(s) { 155 var d = document.createElement('div'); 156 d.textContent = s == null ? '' : String(s); 157 return d.innerHTML; 158 } 159 160 function renderPanel() { 161 var cityOffices = offices[activeCity] || []; 162 var office = cityOffices[Math.min(officeIdx, Math.max(0, cityOffices.length - 1))]; 163 var h = ''; 164 h += '<div class="vb-map-panel__head"><span class="vb-map-panel__dot" aria-hidden="true"></span>' + 165 '<h3 class="vb-map-panel__city">' + esc(names[activeCity] || '') + '</h3></div>'; 166 if (office) { 167 h += '<div class="vb-map-panel__body">'; 168 h += '<div class="vb-map-panel__title">' + esc(office.title) + '</div>'; 169 if (office.address) { 170 h += '<div class="vb-map-panel__row vb-map-panel__row--address">' + PIN_ICON + '<span>' + esc(office.address) + '</span></div>'; 171 } 172 (office.phones || []).forEach(function (p) { 173 h += '<a class="vb-map-panel__phone" href="tel:' + esc(String(p).replace(/[^\d+]/g, '')) + '">' + PHONE_ICON + esc(p) + '</a>'; 174 }); 175 (office.schedule || []).forEach(function (s) { 176 h += '<div class="vb-map-panel__row vb-map-panel__row--schedule">' + CLOCK_ICON + '<span>' + esc(s) + '</span></div>'; 177 }); 178 h += '</div>'; 179 } else { 180 h += '<p class="vb-map-panel__empty">ÐÑÑÑ Ñ ÑÑÐ¾Ð¼Ñ Ð¼ÑÑÑÑ ÑимÑаÑово не пÑÐ¸Ð¹Ð¼Ð°Ñ Ð²ÑдвÑдÑваÑÑв. ТелеÑонÑйÑе: <a href="tel:0800306300">0800 306 300</a></p>'; 181 } 182 if (cityOffices.length > 1) { 183 h += '<div class="vb-map-panel__nav">'; 184 h += '<div class="vb-map-panel__dots">'; 185 cityOffices.forEach(function (_, i) { 186 h += '<button type="button" class="vb-map-panel__dot-btn' + (i === officeIdx ? ' is-active' : '') + '" data-office-idx="' + i + '" aria-label="ÐÑÑÑ ' + (i + 1) + '"></button>'; 187 }); 188 h += '</div>'; 189 h += '<div class="vb-map-panel__arrows">'; 190 h += '<button type="button" class="vb-map-panel__arrow" data-office-nav="-1" aria-label="ÐопеÑеднÑй оÑÑÑ">' + ARROW_LEFT_ICON + '</button>'; 191 h += '<button type="button" class="vb-map-panel__arrow" data-office-nav="1" aria-label="ÐаÑÑÑпний оÑÑÑ">' + ARROW_RIGHT_ICON + '</button>'; 192 h += '</div></div>'; 193 } 194 panel.innerHTML = h; 195 } 196 197 function renderActive() { 198 svg.querySelectorAll('.vb-map__region').forEach(function (p) { 199 p.classList.toggle('is-active', p.getAttribute('data-city') === activeCity); 200 p.setAttribute('aria-pressed', p.getAttribute('data-city') === activeCity ? 'true' : 'false'); 201 }); 202 svg.querySelectorAll('.vb-map__label').forEach(function (g) { 203 g.classList.toggle('is-active', g.getAttribute('data-city') === activeCity); 204 }); 205 } 206 207 function selectCity(id) { 208 if (!id || id === activeCity) return; 209 activeCity = id; 210 officeIdx = 0; 211 renderActive(); 212 renderPanel(); 213 } 214 215 svg.addEventListener('click', function (e) { 216 var region = e.target.closest('.vb-map__region'); 217 if (region) selectCity(region.getAttribute('data-city')); 218 }); 219 svg.addEventListener('keydown', function (e) { 220 if (e.key !== 'Enter' && e.key !== ' ') return; 221 var region = e.target.closest('.vb-map__region'); 222 if (region) { 223 e.preventDefault(); 224 selectCity(region.getAttribute('data-city')); 225 } 226 }); 227 panel.addEventListener('click', function (e) { 228 var dot = e.target.closest('[data-office-idx]'); 229 if (dot) { 230 officeIdx = parseInt(dot.getAttribute('data-office-idx'), 10) || 0; 231 renderPanel(); 232 return; 233 } 234 var nav = e.target.closest('[data-office-nav]'); 235 if (nav) { 236 var cityOffices = offices[activeCity] || []; 237 if (!cityOffices.length) return; 238 var dir = parseInt(nav.getAttribute('data-office-nav'), 10) || 1; 239 officeIdx = (officeIdx + dir + cityOffices.length) % cityOffices.length; 240 renderPanel(); 241 } 242 }); 243 244 renderActive(); 245 renderPanel(); 246 } 247 248 var PIN_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewB
248ox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"></path><circle cx="12" cy="10" r="3"></circle></svg>'; 249 var PHONE_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"></path></svg>'; 250 var CLOCK_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>'; 251 var ARROW_LEFT_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg>'; 252 var ARROW_RIGHT_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg>'; 253 254 document.querySelectorAll('[data-vb-coverage-map]').forEach(initCoverageMap); 255 256 /* ============ FAQ V2 ACCORDION ============ */ 257 document.querySelectorAll('[data-vb-faq]').forEach(function (list) { 258 list.addEventListener('click', function (e) { 259 var summary = e.target.closest('.vb-faq__summary'); 260 if (!summary) return; 261 var item = summary.closest('.vb-faq__item'); 262 var wasOpen = item.classList.contains('is-open'); 263 list.querySelectorAll('.vb-faq__item.is-open').forEach(function (open) { 264 open.classList.remove('is-open'); 265 var btn = open.querySelector('.vb-faq__summary'); 266 if (btn) btn.setAttribute('aria-expanded', 'false'); 267 }); 268 if (!wasOpen) { 269 item.classList.add('is-open'); 270 summary.setAttribute('aria-expanded', 'true'); 271 } 272 }); 273 }); 274 275 /* ============ HERO FADE-IN ============ */ 276 var heroInner = document.querySelector('[data-vb-hero-fade]'); 277 if (heroInner && !reducedMotion) { 278 heroInner.style.opacity = '0'; 279 heroInner.style.transform = 'translateY(24px)'; 280 requestAnimationFrame(function () { 281 heroInner.style.transition = 'opacity .8s ease, transform .8s ease'; 282 heroInner.style.opacity = '1'; 283 heroInner.style.transform = 'translateY(0)'; 284 }); 285 } 286 287 /* ============ GEO: пÑдказки мÑÑÑ Ð¿Ñи ÑокÑÑÑ ============ */ 288 var geoInput = document.getElementById('address-check-input'); 289 var geoHints = document.querySelector('[data-vb-geo-hints]'); 290 291 if (geoInput && geoHints) { 292 var geoHintsShow = function () { 293 if (!geoInput.value.trim()) { geoHints.hidden = false; } 294 }; 295 var geoHintsHide = function () { geoHints.hidden = true; }; 296 297 geoInput.addEventListener('focus', geoHintsShow); 298 299 geoInput.addEventListener('input', function () { 300 // ÐʼÑвивÑÑ ÑекÑÑ â Ð´Ð°Ð»Ñ Ð¿Ñдказки вÑÐ´Ð´Ð°Ñ Ð½Ð°Ñивний Google Places. 301 if (geoInput.value.trim()) { geoHintsHide(); } else { geoHintsShow(); } 302 }); 303 304 // mousedown + preventDefault, а не click: ÑнакÑе blur ÑпÑаÑÑÑ ÑанÑÑе, 305 // ÑпиÑок ÑÑ Ð¾Ð²Ð°ÑÑÑÑÑ, Ñ ÐºÐ»Ñк не дÑйде до кнопки. 306 geoHints.addEventListener('mousedown', function (e) { 307 var btn = e.target.closest('[data-vb-city]'); 308 if (!btn) { return; } 309 e.preventDefault(); 310 geoInput.value = btn.getAttribute('data-vb-city') + ', '; 311 geoHintsHide(); 312 geoInput.focus(); 313 geoInput.dispatchEvent(new Event('input', { bubbles: true })); 314 }); 315 316 geoInput.addEventListener('keydown', function (e) { 317 if (e.key === 'Escape') { geoHintsHide(); } 318 }); 319 320 document.addEventListener('click', function (e) { 321 if (e.target !== geoInput && !geoHints.contains(e.target)) { geoHintsHide(); } 322 }); 323 } 324 325})();
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.