https://www.bitrix24.es/bitrix/templates/landing/js/include_areas/bxw_blocks/bxw_block_migrations.js
1BX.ready(function() { 2 var blocks = document.querySelectorAll('.bxw-block-migrations'); 3 if (!blocks.length) 4 { 5 return; 6 } 7 8 var MOBILE_MEDIA = '(max-width: 991.98px)'; 9 var REDUCED_MOTION_MEDIA = '(prefers-reduced-motion: reduce)'; 10 var APPEAR_INTERVAL_MS = 4000; 11 var PATH_START_DELAY_STEP_MS = 2000; 12 var DEFAULT_PATH_DURATION_SEC = 8; 13 var PATH_DURATION_SEC_BY_LANE = { 14 2: 10, 15 3: 12, 16 4: 10 17 }; 18 var FADE_IN_DISTANCE_PX = 42; 19 var FADE_OUT_DISTANCE_PX = 54; 20 var FADE_OUT_DISTANCE_BY_LANE = { 21 2: 24, 22 3: 20, 23 4: 24 24 }; 25 var DESKTOP_TARGET_LANES = 7; 26 27 function clamp(value, min, max) 28 { 29 return Math.min(Math.max(value, min), max); 30 } 31 32 function initScene(block) 33 { 34 var visual = block.querySelector('.bxw-block-migrations__visual[data-role="scene"]'); 35 if (!visual) 36 { 37 return; 38 } 39 40 var desktopSvg = visual.querySelector('.bxw-block-migrations__paths_desktop'); 41 var mobileSvg = visual.querySelector('.bxw-block-migrations__paths_mobile'); 42 var logos = Array.prototype.slice.call(visual.querySelectorAll('.bxw-block-migrations__logo')); 43 if (!desktopSvg || !mobileSvg || !logos.length) 44 { 45 return; 46 } 47 48 var mobileMq = window.matchMedia(MOBILE_MEDIA); 49 var reducedMotionMq = window.matchMedia(REDUCED_MOTION_MEDIA); 50 var items = []; 51 var rafId = 0; 52 var running = false; 53 var lastFrame = 0; 54 var elapsedMs = 0; 55 var splitCache = Object.create(null); 56 57 function transformPoint(point, matrix) 58 { 59 if (!matrix) 60 { 61 return point; 62 } 63 64 return { 65 x: (point.x * matrix.a) + (point.y * matrix.c) + matrix.e, 66 y: (point.x * matrix.b) + (point.y * matrix.d) + matrix.f, 67 }; 68 } 69 70 function pathPointToVisual(path, point) 71 { 72 var matrix = path.getScreenCTM(); 73 if (!matrix) 74 { 75 return { x: 0, y: 0 }; 76 } 77 78 var screenPoint = transformPoint(point, matrix); 79 var visualRect = visual.getBoundingClientRect(); 80 81 return { 82 x: screenPoint.x - visualRect.left, 83 y: screenPoint.y - visualRect.top, 84 }; 85 } 86 87 function clearGeneratedSplitPaths(svg) 88 { 89 var generated = svg.querySelectorAll('.bxw-block-migrations__split-path'); 90 for (var i = 0; i < generated.length; i++) 91 { 92 generated[i].remove(); 93 } 94 } 95 96 function samplePathPoints(pathEl, reverse) 97 { 98 var points = []; 99 var len = pathEl.getTotalLength(); 100 var step = Math.max(8, len / 40); 101 var d; 102 var finalPoint = reverse ? pathEl.getPointAtLength(0) : pathEl.getPointAtLength(len); 103 104 if (reverse) 105 { 106 for (d = len; d >= 0; d -= step) 107 { 108 points.push(pathEl.getPointAtLength(d)); 109 } 110 } 111 else 112 { 113 for (d = 0; d <= len; d += step) 114 { 115 points.push(pathEl.getPointAtLength(d)); 116 } 117 } 118 119 if (!points.length || points[points.length - 1].x !== finalPoint.x || points[points.length - 1].y !== finalPoint.y) 120 { 121 points.push(finalPoint); 122 } 123 124 return points; 125 } 126 127 function composePathFromSegments(parent, ns, pathA, reverseA, pathB, reverseB) 128 { 129 var pointsA = samplePathPoints(pathA, reverseA); 130 var pointsB = samplePathPoints(pathB, reverseB); 131 var points = pointsA.concat(pointsB); 132 if (!points.length) 133 { 134 return null; 135 } 136 137 var d = 'M' + points[0].x + ' ' + points[0].y; 138 for (var i = 1; i < points.length; i++) 139 { 140 d += 'L' + points[i].x + ' ' + points[i].y; 141 } 142 143 var merged = document.createElementNS(ns, 'path'); 144 merged.setAttribute('d', d); 145 merged.setAttribute('fill', 'none'); 146 merged.setAttribute('stroke', 'none'); 147 merged.setAttribute('stroke-width', '2'); 148 merged.setAttribute('class', 'bxw-block-migrations__split-path'); 149 parent.appendChild(merged); 150 151 return merged; 152 } 153 154 function getPathVariants(path) 155 { 156 if (!path) 157 { 158 return []; 159 } 160 161 var cacheKey = path.id || path.getAttribute('d') || ('path-' + Math.random()); 162 if (splitCache[cacheKey]) 163 { 164 return splitCache[cacheKey]; 165 } 166 167 var d = path.getAttribute('d') || ''; 168 var chunks = d.match(/[Mm][^Mm]*/g) || []; 169 if (chunks.length <= 1) 170 { 171 splitCache[cacheKey] = [path]; 172 return splitCache[cacheKey]; 173 } 174 175 var strict = []; 176 var loose = []; 177 var fallback = []; 178 var splitPaths = []; 179 var parent = path.parentNode; 180 var ns = path.namespaceURI || 'http://www.w3.org/2000/svg'; 181 182 for (var i = 0; i < chunks.length; i++) 183 { 184 var splitPath = document.createElementNS(ns, 'path'); 185 splitPath.setAttribute('d', chunks[i].trim()); 186 splitPath.setAttribute('fill', 'none'); 187 splitPath.setAttribute('stroke', 'none'); 188 splitPath.setAttribute('stroke-width', '2'); 189 splitPath.setAttribute('class', 'bxw-block-migrations__split-path'); 190 parent.appendChild(splitPath); 191 splitPaths.push(splitPath); 192 193 var splitLen = splitPath.getTotalLength(); 194 var pStart = pathPointToVisual(splitPath, splitPath.getPointAtLength(0)); 195 var pEnd = pathPointToVisual(splitPath, splitPath.getPointAtLength(splitLen)); 196 var horizontalSpan = Math.abs(pEnd.x - pStart.x); 197 198 if (splitLen > 300 && horizontalSpan >= 120) 199 { 200 strict.push(splitPath); 201 } 202 if (splitLen > 180 && horizontalSpan >
202= 80) 203 { 204 loose.push(splitPath); 205 } 206 if (splitLen > 100) 207 { 208 fallback.push(splitPath); 209 } 210 } 211 212 // Fixed mapping for source SVGs with 8 chunks (desktop/mobile), 7 lanes: 213 // 1: chunk0 214 // 2: chunk1 215 // 3: chunk3 + chunk4 216 // 4: chunk2 217 // 5: reverse(chunk5) + chunk4 218 // 6: chunk7 219 // 7: chunk6 220 var sourceId = path.id || ''; 221 var isSourcePath = (sourceId.indexOf('-path-d-source') !== -1) || (sourceId.indexOf('-path-m-source') !== -1); 222 if (isSourcePath && splitPaths.length >= 8) 223 { 224 var mergedTopMiddle = composePathFromSegments(parent, ns, splitPaths[3], false, splitPaths[4], false); 225 var mergedBottomMiddle = composePathFromSegments(parent, ns, splitPaths[5], true, splitPaths[4], false); 226 if (mergedTopMiddle && mergedBottomMiddle) 227 { 228 var fixed = [ 229 splitPaths[0], 230 splitPaths[1], 231 mergedTopMiddle, 232 splitPaths[2], 233 mergedBottomMiddle, 234 splitPaths[7], 235 splitPaths[6] 236 ]; 237 splitCache[cacheKey] = fixed; 238 return fixed; 239 } 240 } 241 242 var result = strict; 243 if (result.length < DESKTOP_TARGET_LANES) 244 { 245 result = loose; 246 } 247 if (result.length < DESKTOP_TARGET_LANES) 248 { 249 result = fallback; 250 } 251 252 // Stabilize lane order independently from DOM order: 253 // sort by Y of the funnel-side endpoint (left-most endpoint). 254 result = result.slice().sort(function(pathA, pathB) { 255 function getSinkY(pathEl) 256 { 257 var len = pathEl.getTotalLength(); 258 var p0 = pathPointToVisual(pathEl, pathEl.getPointAtLength(0)); 259 var p1 = pathPointToVisual(pathEl, pathEl.getPointAtLength(len)); 260 return (p0.x <= p1.x) ? p0.y : p1.y; 261 } 262 263 var aY = getSinkY(pathA); 264 var bY = getSinkY(pathB); 265 if (aY === bY) 266 { 267 return pathA.getTotalLength() - pathB.getTotalLength(); 268 } 269 270 return aY - bY; 271 }); 272 273 if (result.length > DESKTOP_TARGET_LANES) 274 { 275 result = result.slice(0, DESKTOP_TARGET_LANES); 276 } 277 if (!result.length) 278 { 279 result = [path]; 280 } 281 282 splitCache[cacheKey] = result; 283 return result; 284 } 285 286 function getDirectionalPathPoint(item, progress) 287 { 288 var t = item.reverse ? (1 - progress) : progress; 289 return item.path.getPointAtLength(item.length * t); 290 } 291 292 function placeLogo(item, progress, isStatic) 293 { 294 var pointRaw = getDirectionalPathPoint(item, progress); 295 var position = pathPointToVisual(item.path, pointRaw); 296 297 var distanceFromStart = progress * item.length; 298 var distanceToEnd = (1 - progress) * item.length; 299 var fadeIn = clamp(distanceFromStart / FADE_IN_DISTANCE_PX, 0, 1); 300 var laneFadeOutDistance = (typeof FADE_OUT_DISTANCE_BY_LANE[item.laneIndex] === 'number') 301 ? FADE_OUT_DISTANCE_BY_LANE[item.laneIndex] 302 : FADE_OUT_DISTANCE_PX; 303 var fadeOut = clamp(distanceToEnd / laneFadeOutDistance, 0, 1); 304 var visibility = isStatic ? 1 : Math.min(fadeIn, fadeOut); 305 var scale = 0.68 + (visibility * 0.32); 306 307 item.el.style.opacity = visibility.toFixed(3); 308 item.el.style.transform = 'translate(' + position.x.toFixed(2) + 'px, ' + position.y.toFixed(2) + 'px) translate(-50%, -50%) scale(' + scale.toFixed(3) + ')'; 309 } 310 311 function rebuildItems() 312 { 313 var isMobile = mobileMq.matches; 314 var activeSvg = isMobile ? mobileSvg : desktopSvg; 315 splitCache = Object.create(null); 316 clearGeneratedSplitPaths(activeSvg); 317 318 var laneCounters = Object.create(null); 319 items = logos.map(function(el, index) { 320 var pathSelector = isMobile ? el.getAttribute('data-path-mobile') : el.getAttribute('data-path-desktop'); 321 if (!pathSelector) 322 { 323 return null; 324 } 325 326 var path = activeSvg.querySelector(pathSelector); 327 if (!path) 328 { 329 return null; 330 } 331 332 var variants = getPathVariants(path); 333 var laneCount = variants.length || 1; 334 var forcedLane = parseInt(el.getAttribute('data-lane-desktop') || '', 10); 335 var laneIndex; 336 337 if (!isMobile && !isNaN(forcedLane) && forcedLane >= 0 && forcedLane < laneCount) 338 { 339 laneIndex = forcedLane; 340 } 341 else 342 { 343 laneIndex = variants.length ? (index % variants.length) : 0; 344 } 345 346 var selectedPath = variants.length ? variants[laneIndex] : path; 347 // Path coordinates, not screen ones: logos must travel toward the funnel neck even when RTL mirrors the scene. 348 var startPoint = selectedPath.getPointAtLength(0); 349 var endPoint = selectedPath.getPointAtLength(selectedPath.getTotalLength()); 350 var reverse = isMobile ? (endPoint.y < startPoint.y) : (endPoint.x > startPoint.x); 351 var laneKey = laneIndex + '::' + (selectedPath.getAttribute('d') || selectedPath.id || 'lane'); 352 var laneOrder = laneCounters[laneKey] || 0; 353 laneCounters[laneKey] = laneOrder + 1; 354 355 var durationSec = (!isMobile && typeof PATH_DURATION_SEC_BY_LANE[laneIndex] === 'number') 356 ? PATH_DURATION_SEC_BY_LANE[laneIndex] 357 : DEFAULT_PATH_DURATION_SEC; 358 var speedPxMs = selectedPath.getTotalLength() / (durationSec * 1000); 359 var delayMs = (laneIndex * PATH_START_DELAY_STEP_MS) + (laneOrder * APPEAR_INTERVAL_MS); 360 361 return { 362 el: el, 363 path: selectedPath, 364 length: selectedPath.getTotalLength(), 365 phase: 0, 366 delayMs: delayMs, 367 speedPxMs: speedPxMs,
368 laneIndex: laneIndex, 369 reverse: reverse, 370 }; 371 }).filter(function(item) { 372 return !!item; 373 }); 374 375 if (!items.length) 376 { 377 return; 378 } 379 380 items.forEach(function(item, index) { 381 if (reducedMotionMq.matches) 382 { 383 placeLogo(item, ((index % 8) / 8), true); 384 return; 385 } 386 387 if (elapsedMs < item.delayMs) 388 { 389 item.el.style.opacity = '0'; 390 return; 391 } 392 393 var activeMs = elapsedMs - item.delayMs; 394 item.phase = ((activeMs * item.speedPxMs) / item.length) % 1; 395 placeLogo(item, item.phase, false); 396 }); 397 } 398 399 function stop() 400 { 401 running = false; 402 if (rafId) 403 { 404 window.cancelAnimationFrame(rafId); 405 rafId = 0; 406 } 407 } 408 409 function tick(timestamp) 410 { 411 if (!running) 412 { 413 return; 414 } 415 416 if (!lastFrame) 417 { 418 lastFrame = timestamp; 419 } 420 421 var delta = Math.min(timestamp - lastFrame, 64); 422 lastFrame = timestamp; 423 elapsedMs += delta; 424 425 for (var i = 0; i < items.length; i++) 426 { 427 var item = items[i]; 428 if (elapsedMs < item.delayMs) 429 { 430 item.el.style.opacity = '0'; 431 continue; 432 } 433 434 var activeMs = elapsedMs - item.delayMs; 435 item.phase = ((activeMs * item.speedPxMs) / item.length) % 1; 436 placeLogo(item, item.phase, false); 437 } 438 439 rafId = window.requestAnimationFrame(tick); 440 } 441 442 function start() 443 { 444 if (running || reducedMotionMq.matches) 445 { 446 return; 447 } 448 449 running = true; 450 lastFrame = 0; 451 rafId = window.requestAnimationFrame(tick); 452 } 453 454 function onResizeOrMediaChange() 455 { 456 rebuildItems(); 457 if (!reducedMotionMq.matches && running) 458 { 459 lastFrame = 0; 460 } 461 } 462 463 function onVisibilityChange() 464 { 465 if (document.hidden) 466 { 467 stop(); 468 return; 469 } 470 471 if (!reducedMotionMq.matches) 472 { 473 start(); 474 } 475 } 476 477 rebuildItems(); 478 start(); 479 480 window.addEventListener('resize', onResizeOrMediaChange); 481 document.addEventListener('visibilitychange', onVisibilityChange); 482 483 if (typeof mobileMq.addEventListener === 'function') 484 { 485 mobileMq.addEventListener('change', onResizeOrMediaChange); 486 reducedMotionMq.addEventListener('change', onResizeOrMediaChange); 487 } 488 else 489 { 490 mobileMq.addListener(onResizeOrMediaChange); 491 reducedMotionMq.addListener(onResizeOrMediaChange); 492 } 493 } 494 495 blocks.forEach(initScene); 496});
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.