PageSourceSearch

https://www.bitrix24.es/bitrix/templates/landing/js/include_areas/bxw_blocks/bxw_block_migrations.js

js bitrix24.es collected 2026-09-24 20:19:19 UTC 12,518 bytes, 496 lines download raw bytes

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.