PageSourceSearch

https://znaktech.ru/wp-content/plugins/zntech-neural-canvas/assets/neural-canvas.js?ver=1788969265

js znaktech.ru collected 2026-10-10 00:44:03 UTC 22,257 bytes, 786 lines download raw bytes

1(function () {
2  'use strict';
3
4  var reduceMotion = window.matchMedia &&
5    window.matchMedia('(prefers-reduced-motion: reduce)').matches;
6  var mobileQuery = window.matchMedia ? window.matchMedia('(max-width: 767px)') : null;
7
8  function NeuralCanvas(root) {
9    this.root = root;
10    this.canvas = root.querySelector('.zntech-neural-canvas__canvas');
11    this.ctx = this.canvas ? this.canvas.getContext('2d') : null;
12    this.nodes = [];
13    this.raf = 0;
14    this.width = 0;
15    this.height = 0;
16    this.dpr = 1;
17    this.hidden = false;
18    this.time = 0;
19    this.focusBox = null;
20    this.focusTargetBox = null;
21    this.options = readOptions(root);
22
23    if (!this.canvas || !this.ctx) {
24      return;
25    }
26
27    mountBackground(root);
28
29    if (isMobileViewport()) {
30      root.classList.add('zntech-neural-canvas--disabled-mobile');
31      return;
32    }
33
34    this.resize();
35    this.bind();
36
37    if (isMobileViewport() && this.options.mobile === 'static') {
38      this.drawStatic();
39    } else if (!reduceMotion) {
40      this.draw();
41    } else {
42      this.drawStatic();
43    }
44  }
45
46  NeuralCanvas.prototype.bind = function () {
47    var self = this;
48
49    if (window.ResizeObserver) {
50      this.resizeObserver = new ResizeObserver(function () {
51        self.resize();
52      });
53      this.resizeObserver.observe(this.root);
54    } else {
55      window.addEventListener('resize', function () {
56        self.resize();
57      });
58    }
59
60    document.addEventListener('visibilitychange', function () {
61      self.hidden = document.hidden;
62
63      if (self.hidden) {
64        window.cancelAnimationFrame(self.raf);
65        self.raf = 0;
66        return;
67      }
68
69      if (!reduceMotion && !self.raf) {
70        self.draw();
71      }
72    });
73
74    this.root.addEventListener('zntech-neural-update', function () {
75      self.options = readOptions(self.root);
76      self.initNodes();
77
78      if (reduceMotion) {
79        self.drawStatic();
80      }
81    });
82  };
83
84  NeuralCanvas.prototype.resize = function () {
85    var rect = this.root.getBoundingClientRect();
86    var width = Math.max(1, Math.floor(rect.width || this.root.offsetWidth || window.innerWidth));
87    var height = Math.max(1, Math.floor(rect.height || this.root.offsetHeight || this.options.height || 520));
88
89    if (width === this.width && height === this.height) {
90      return;
91    }
92
93    this.width = width;
94    this.height = height;
95    this.dpr = Math.min(2, window.devicePixelRatio || 1);
96    this.canvas.width = Math.floor(width * this.dpr);
97    this.canvas.height = Math.floor(height * this.dpr);
98    this.canvas.style.width = width + 'px';
99    this.canvas.style.height = height + 'px';
100    this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
101    this.initNodes();
102
103    if (reduceMotion) {
104      this.drawStatic();
105    }
106  };
107
108  NeuralCanvas.prototype.initNodes = function () {
109    var focus = getFocusBoxes(this);
110    var areaWidth = focus.box ? focus.box.width : this.width;
111    var areaHeight = focus.box ? focus.box.height : this.height;
112    var count = Math.min(
113      this.options.maxNodes,
114      Math.max(8, Math.floor((areaWidth * areaHeight) / this.options.density))
115    );
116
117    if (focus.box && this.options.effect === 'circuit') {
118      count = Math.min(this.options.maxNodes, Math.max(20, Math.round(count * 1.75)));
119    }
120
121    this.focusBox = focus.box;
122    this.focusTargetBox = focus.targetBox;
123    this.nodes = [];
124
125    for (var i = 0; i < count; i += 1) {
126      var paint = pickNodePaint(this.options);
127      var point = focusedPoint(this);
128
129      this.nodes.push({
130        x: point.x,
131        y: point.y,
132        vx: (Math.random() - 0.5) * this.options.speed,
133        vy: (Math.random() - 0.5) * this.options.speed,
134        r: (Math.random() * 2 + 1) * this.options.nodeScale,
135        bounds: point.bounds || null,
136        colorType: paint.type,
137        dotColor: paint.dotColor,
138        glowColor: paint.glowColor
139      });
140    }
141  };
142
143  NeuralCanvas.prototype.drawStatic = function () {
144    this.paint(false);
145  };
146
147  NeuralCanvas.prototype.draw = function () {
148    var self = this;
149
150    if (this.hidden || isMobileViewport()) {
151      this.root.classList.add('zntech-neural-canvas--disabled-mobile');
152      this.raf = 0;
153      return;
154    }
155
156    this.time = window.performance ? window.performance.now() : Date.now();
157    this.paint(true);
158    this.raf = window.requestAnimationFrame(function () {
159      self.draw();
160    });
161  };
162
163  NeuralCanvas.prototype.paint = function (animate) {
164    var ctx = this.ctx;
165    var options = this.options;
166
167    ctx.clearRect(0, 0, this.width, this.height);
168
169    if (options.background !== 'transparent') {
170      ctx.fillStyle = options.background;
171      ctx.fillRect(0, 0, this.width, this.height);
172    }
173
174    if (options.effect === 'circuit') {
175      this.paintCircuit(animate);
176      return;
177    }
178
179    if (options.effect === 'scanner') {
180      this.paintNetwork(animate, 0.68);
181      this.paintScanner();
182      return;
183    }
184
185    if (options.effect === 'hex') {
186      this.paintHex();
187      return;
188    }
189
190    this.paintNetwork(animate, 1);
191  };
192
193  NeuralCanvas.prototype.paintNetwork = function (animate, intensity) {
194    var ctx = this.ctx;
195    var options = this.options;
196    var lineAlpha = options.lineAlpha * (intensity || 1);
197
198    for (var i = 0; i < this.nodes.length; i += 1) {
199      var a = this.nodes[i];
200
201      if (animate) {
202        a.x += a.vx;
203        a.y += a.vy;
204
205        if (a.x < 0 || a.x > this.width) {
206          a.vx *= -1;
207        }
208
209        if (a.y < 0 || a.y > this.height) {
210          a.vy *= -1;
211        }
212      }
213
214      for (var j = i + 1; j < this.nodes.length; j += 1) {
215        var b = this.nodes[j];
216        var dx = a.x - b.x;
217        var dy = a.y - b.y;
218        var distance = Math.sqrt(dx * dx + dy * dy);
219
220        if (distance < options.lineDistance) {
221          ctx.strokeStyle = 'rgba(' + lineColorFor(a, b, options) + ',' + ((1 - distance / options.lineDistance) * lineAlpha) + ')';
222          ctx.lineWidth = 1;
223          ctx.beginPath();
224          ctx.moveTo(a.x, a.y);
225          ctx.lineTo(b.x, b.y);
226          ctx.stroke();
227        }
228      }
229
230      var gradient = ctx.createRadialGradient(a.x, a.y, 0, a.x, a.y, a.r * 3);
231      gradient.addColorStop(0, 'rgba(' + a.dotColor + ',0.85)');
232      gradient.addColorStop(1, 'rgba(' + a.glowColor + ',0)');
233      ctx.fillStyle = gradient;
234      ctx.beginPath();
235      ctx.arc(a.x, a.y, a.r * 2.4, 0, Math.PI * 2);
236      ctx.fill();
237    }
238  };
239
240  NeuralCanvas.prototype.paintCircuit = function (animate) {
241    var ctx = this.ctx;
242    var options = this.options;
243    var maxLinksPerNode = 3;
244    var clipBox = this.focusBox && options.focus.indexOf('panel') === 0 ? this.focusBox : null;
245
246    if (clipBox) {
247      ctx.save();
248      ctx.beginPath();
249      ctx.rect(clipBox.x, clipBox.y, clipBox.width, clipBox.height);
250      ctx.clip();
251    }
252
253    for (var i = 0; i < this.nodes.length; i += 1) {
254      var a = this.nodes[i];
255      var links = 0;
256
257      if (animate) {
258        a.x += a.vx * 0.18;
259        a.y += a.vy * 0.18;
260        bounceNode(a, a.bounds || clipBox || { x: 0, y: 0, width: this.width, height: this.height });
261      }
262
263      for (var j = i + 1; j < this.nodes.length && links < maxLinksPerNode; j += 1) {
264        var b = this.nodes[j];
265        var dx = a.x - b.x;
266        var dy = a.y - b.y;
267        var distance = Math.sqrt(dx * dx + dy * dy);
268        var limit = options.lineDistance * 1.18;
269
270        if (distance >= limit) {
271          continue;
272        }
273
274        links += 1;
275        var horizontalFirst = ((i + j) % 2) === 0;
276        var mid = horizontalFirst ? { x: b.x, y: a.y } : { x: a.x, y: b.y };
277        var alpha = (1 - distance / limit) * options.lineAlpha * 0.92;
278        var color = lineColorFor(a, b, options);
279
280        ctx.strokeStyle = 'rgba(' + color + ',' + alpha + ')';
281        ctx.lineWidth = Math.max(1, Math.min(1.25, options.nodeScale));
282        ctx.beginPath();
283        ctx.moveTo(a.x, a.y);
284        ctx.lineTo(mid.x, mid.y);
285        ctx.lineTo(b.x, b.y);
286        ctx.stroke();
287
288        var pulsePhase = (this.time * options.pulseSpeed / 5200 + i * 0.13 + j * 0.07) % 1;
289        var pulse = pointOnPolyline(a, mid, b, pulsePhase);
290
291        ctx.fillStyle = 'rgba(' + color + ',0.55)';
292        ctx.beginPath();
293        ctx.arc(pulse.x, pulse.y, 2.2 * options.nodeScale, 0, Math.PI * 2);
294        ctx.fill();
295      }
296
297      drawCircuitNode(ctx, a);
298    }
299
300    if (clipBox) {
301      ctx.restore();
302    }
303  };
304
305  NeuralCanvas.prototype.paintScanner = function () {
306    var ctx = this.ctx;
307    var x = ((this.time * 0.06) % (this.width + 260)) - 130;
308    var width = 150;
309    var gradient = ctx.createLinearGradient(x - width, 0, x + width, 0);
310
311    gradient.addColorStop(0, 'rgba(190,25,90,0)');
312    gradient.addColorStop(0.5, 'rgba(190,25,90,0.11)');
313    gradient.addColorStop(1, 'rgba(190,25,90,0)');
314
315    ctx.fillStyle = gradient;
316    ctx.fillRect(x - width, 0, width * 2, this.height);
317
318    ctx.strokeStyle = 'rgba(190,25,90,0.32)';
319    ctx.lineWidth = 1;
320    ctx.beginPath();
321    ctx.moveTo(x, 0);
322    ctx.lineTo(x, this.height);
323    ctx.stroke();
324  };
325
326  NeuralCanvas.prototype.paintHex = function () {
327    var ctx = this.ctx;
328    var options = this.options;
329    var radius = Math.max(42, Math.min(78, Math.floor(Math.min(this.width, this.height) / 11)));
330    var hexHeight = Math.sqrt(3) * radius;
331    var stepX = radius * 1.5;
332    var time = this.time / 1000;
333
334    ctx.lineWidth = 1;
335
336    for (var y = -hexHeight; y < this.height + hexHeight; y += hexHeight) {
337      var row = Math.round(y / hexHeight);
338
339      for (var x = -radius; x < this.width + radius; x += stepX) {
340        var cx = x + (row % 2 ? radius * 0.75 : 0);
341        var cy = y;
342        var wave = (Math.sin(time * 0.9 + cx * 0.012 + cy * 0.01) + 1) / 2;
343        var alpha = 0.045 + wave * options.lineAlpha * 0.23;
344
345        ctx.strokeStyle = 'rgba(' + options.lineColor + ',' + alpha + ')';
346        drawHexPath(ctx, cx, cy, radius);
347        ctx.stroke();
348
349        if (wave > 0.92) {
350          ctx.fillStyle = 'rgba(' + options.redColor + ',0.52)';
351          ctx.beginPath();
352          ctx.arc(cx + radius, cy, 2.4, 0, Math.PI * 2);
353          ctx.fill();
354        } else if (wave > 0.82) {
355          ctx.fillStyle = 'rgba(' + options.blueGrayColor + ',0.38)';
356          ctx.beginPath();
357          ctx.arc(cx - radius * 0.5, cy + hexHeight * 0.5, 2.1, 0, Math.PI * 2);
358          ctx.fill();
359        }
360      }
361    }
362  };
363
364  function readOptions(root) {
365    return {
366      density: readNumber(root, 'density', 17000),
367      maxNodes: readNumber(root, 'maxNodes', 64),
368      speed: readFloat(root, 'speed', 0.4),
369      pulseSpeed: clamp(readFloat(root, 'pulseSpeed', 1), 0, 2),
370      palette: root.dataset.palette === 'brand' ? 'brand' : 'dark',
371      effect: readEffect(root),
372      focus: readFocus(root),
373      focusSelector: root.dataset.focusSelector || '.zntech-hero-stage__panel',
374      focusPadding: readNumber(root, 'focusPadding', 190),
375      nodeScale: clamp(readFloat(root, 'nodeScale', 1), 0.6, 2),
376      mobile: readMobile(root),
377      lineDistance: readNumber(root, 'lineDistance', 130),
378      lineAlpha: readFloat(root, 'lineAlpha', 0.32),
379      lineColor: readRgb(root, 'lineColor', '30,40,75'),
380      dotColor: readRgb(root, 'dotColor', '30,40,75'),
381      glowColor: readRgb(root, 'glowColor', '64,88,145'),
382      redColor: readRgb(root, 'redColor', '190,25,90'),
383      blueGrayColor: readRgb(root, 'blueGrayColor', '125,145,170'),
384      redRatio: clamp(readFloat(root, 'redRatio', 0.1), 0, 0.35),
385      blueGrayRatio: clamp(readFloat(root, 'blueGrayRatio', 0.2), 0, 0.55),
386      background: root.dataset.background || 'transparent',
387      height: parseInt(getComputedStyle(root).getPropertyValue('--zntech-neural-height'), 10) || 520
388    };
389  }
390
391  function readEffect(root) {
392    var effect = root.dataset.effect || 'network';
393
394    return /^(network|circuit|scanner|hex)$/.test(effect) ? effect : 'network';
395  }
396
397  function readFocus(root) {
398    var focus = root.dataset.focus || 'none';
399
400    return /^(none|center|right|panel|panel-right-bottom)$/.test(focus) ? focus : 'none';
401  }
402
403  function readMobile(root) {
404    var mobile = root.dataset.mobile || 'off';
405
406    return /^(off|static|on)$/.test(mobile) ? mobile : 'off';
407  }
408
409  function isMobileViewport() {
410    return !!(mobileQuery && mobileQuery.matches);
411  }
412
413  function pickNodePaint(options) {
414    if (options.palette !== 'brand') {
415      return {
416        type: 'dark',
417        dotColor: options.dotColor,
418        glowColor: options.glowColor
419      };
420    }
421
422    var redRatio = options.redRatio;
423    var blueGrayRatio = Math.min(options.blueGrayRatio, Math.max(0, 0.9 - redRatio));
424    var roll = Math.random();
425
426    if (roll < redRatio) {
427      return {
428        type: 'red',
429        dotColor: options.redColor,
430        glowColor: options.redColor
431      };
432    }
433
434    if (roll < redRatio + blueGrayRatio) {
435      return {
436        type: 'blue-gray',
437        dotColor: options.blueGrayColor,
438        glowColor: options.blueGrayColor
439      };
440    }
441
442    return {
443      type: 'dark',
444      dotColor: options.dotColor,
445      glowColor: options.glowColor
446    };
447  }
448
449  function lineColorFor(a, b, options) {
450    if (options.palette !== 'brand') {
451      return options.lineColor;
452    }
453
454    if (a.colorType === 'red' || b.colorType === 'red') {
455      return options.redColor;
456    }
457
458    if (a.colorType === 'blue-gray' || b.colorType === 'blue-gray') {
459      return options.blueGrayColor;
460    }
461
462    return options.lineColor;
463  }
464
465  function drawCircuitNode(ctx, node) {
466    var size = Math.max(3, node.r * 2.3);
467    var half = size / 2;
468
469    ctx.fillStyle = 'rgba(' + node.dotColor + ',0.48)';
470    ctx.fillRect(node.x - half, node.y - half, size, size);
471
472    ctx.strokeStyle = 'rgba(' + node.glowColor + ',0.2)';
473    ctx.strokeRect(node.x - half - 2, node.y - half - 2, size + 4, size + 4);
474  }
475
476  function bounceNode(node, box) {
477    var minX = box.x;
478    var maxX = box.x + box.width;
479    var minY = box.y;
480    var maxY = box.y + box.height;
481
482    if (node.x < minX) {
483      node.x = minX;
484      node.vx = Math.abs(node.vx);
485    } else if (node.x > maxX) {
486      node.x = maxX;
487      node.vx = -Math.abs(node.vx);
488    }
489
490    if (node.y < minY) {
491      node.y = minY;
492      node.vy = Math.abs(node.vy);
493    } else if (node.y > maxY) {
494      node.y = maxY;
495      node.vy = -Math.abs(node.vy);
496    }
497  }
498
499  function getFocusBoxes(instance) {
500    var options = instance.options;
501    var rootBox = instance.root.getBoundingClientRect();
502    var pad = Math.max(40, options.focusPadding || 130);
503    var targetBox = null;
504    var box = null;
505
506    if (options.focus === 'panel' || options.focus === 'panel-right-bottom') {
507      targetBox = panelFocusBox(instance.root, rootBox, options.focusSelector);
508
509      if (targetBox) {
510        box = clampBox({
511          x: targetBox.x - pad,
512          y: targetBox.y - pad,
513          width: targetBox.width + pad * 2,
514          height: targetBox.height + pad * 2
515        }, instance.width, instance.height);
516      }
517    } else if (options.focus === 'right') {
518      box = {
519        x: instance.width * 0.48,
520        y: instance.height * 0.06,
521        width: instance.width * 0.5,
522        height: instance.height * 0.88
523      };
524    } else if (options.focus === 'center') {
525      box = {
526        x: instance.width * 0.18,
527        y: instance.height * 0.12,
528        width: instance.width * 0.64,
529        height: instance.height * 0.76
530      };
531    }
532
533    return {
534      box: box ? clampBox(box, instance.width, instance.height) : null,
535      targetBox: targetBox
536    };
537  }
538
539  function panelFocusBox(root, rootBox, selector) {
540    var target = findFocusTarget(root, selector);
541
542    if (!target) {
543      return null;
544    }
545
546    var rect = target.getBoundingClientRect();
547
548    if (!rect.width || !rect.height) {
549      return null;
550    }
551
552    return {
553      x: rect.left - rootBox.left,
554      y: rect.top - rootBox.top,
555      width: rect.width,
556      height: rect.height
557    };
558  }
559
560  function findFocusTarget(root, selector) {
561    if (!selector) {
562      return null;
563    }
564
565    try {
566      if (root.parentElement) {
567        var local = root.parentElement.querySelector(selector);
568
569        if (local) {
570          return local;
571        }
572      }
573
574      return document.querySelector(selector);
575    } catch (error) {
576      return null;
577    }
578  }
579
580  function focusedPoint(instance) {
581    if (instance.focusTargetBox && instance.options.focus === 'panel-right-bottom') {
582      return pointRightBottomOfTarget(instance.focusTargetBox, instance.options.focusPadding, instance.width, instance.height);
583    }
584
585    if (instance.focusTargetBox && instance.options.focus === 'panel') {
586      return pointAroundTarget(instance.focusTargetBox, instance.options.focusPadding, instance.width, instance.height);
587    }
588
589    if (instance.focusBox) {
590      return randomPointInBox(instance.focusBox, instance.width, instance.height);
591    }
592
593    return {
594      x: Math.random() * instance.width,
595      y: Math.random() * instance.height
596    };
597  }
598
599  function pointAroundTarget(target, padding, maxWidth, maxHeight) {
600    var pad = Math.max(40, padding || 130);
601    var gap = Math.min(28, pad * 0.18);
602    var side = Math.floor(Math.random() * 4);
603    var x;
604    var y;
605
606    if (side === 0) {
607      x = randomBetween(target.x - pad * 0.22, target.x + target.width + pad * 0.22);
608      y = randomBetween(target.y - pad, target.y - gap);
609    } else if (side === 1) {
610      x = randomBetween(target.x + target.width + gap, target.x + target.width + pad);
611      y = randomBetween(target.y - pad * 0.18, target.y + target.height + pad * 0.18);
612    } else if (side === 2) {
613      x = randomBetween(target.x - pad * 0.22, target.x + target.width + pad * 0.22);
614      y = randomBetween(target.y + target.height + gap, target.y + target.height + pad);
615    } else {
616      x = randomBetween(target.x - pad, target.x - gap);
617      y = randomBetween(target.y - pad * 0.18, target.y + target.height + pad * 0.18);
618    }
619
620    return {
621      x: clamp(x, 0, maxWidth),
622      y: clamp(y, 0, maxHeight)
623    };
624  }
625
626  function pointRightBottomOfTarget(target, padding, maxWidth, maxHeight) {
627    var pad = Math.max(40, padding || 130);
628    var gap = Math.min(28, pad * 0.18);
629    var rightBox = clampBox({
630      x: target.x + target.width + gap,
631      y: target.y + target.height * 0.04,
632      width: pad - gap,
633      height: target.height * 0.96 + pad * 0.32
634    }, maxWidth, maxHeight);
635    var bottomBox = clampBox({
636      x: target.x - pad * 0.08,
637      y: target.y + target.height + gap,
638      width: target.width + pad * 0.36,
639      height: pad - gap
640    }, maxWidth, maxHeight);
641    var box = Math.random() < 0.54 ? rightBox : bottomBox;
642    var point = randomPointInBox(box, maxWidth, maxHeight);
643
644    point.bounds = box;
645    return point;
646  }
647
648  function randomPointInBox(box, maxWidth, maxHeight) {
649    return {
650      x: clamp(randomBetween(box.x, box.x + box.width), 0, maxWidth),
651      y: clamp(randomBetween(box.y, box.y + box.height), 0, maxHeight)
652    };
653  }
654
655  function randomBetween(min, max) {
656    if (max < min) {
657      var tmp = min;
658      min = max;
659      max = tmp;
660    }
661
662    return min + Math.random() * (max - min);
663  }
664
665  function clampBox(box, maxWidth, maxHeight) {
666    var x = clamp(box.x, 0, maxWidth);
667    var y = clamp(box.y, 0, maxHeight);
668    var right = clamp(box.x + box.width, 0, maxWidth);
669    var bottom = clamp(box.y + box.height, 0, maxHeight);
670
671    return {
672      x: x,
673      y: y,
674      width: Math.max(1, right - x),
675      height: Math.max(1, bottom - y)
676    };
677  }
678
679  function pointOnPolyline(a, mid, b, t) {
680    var firstLength = distanceBetween(a, mid);
681    var secondLength = distanceBetween(mid, b);
682    var total = firstLength + secondLength || 1;
683    var target = total * t;
684
685    if (target <= firstLength) {
686      return lerpPoint(a, mid, target / (firstLength || 1));
687    }
688
689    return lerpPoint(mid, b, (target - firstLength) / (secondLength || 1));
690  }
691
692  function distanceBetween(a, b) {
693    var dx = a.x - b.x;
694    var dy = a.y - b.y;
695
696    return Math.sqrt(dx * dx + dy * dy);
697  }
698
699  function lerpPoint(a, b, t) {
700    return {
701      x: a.x + (b.x - a.x) * t,
702      y: a.y + (b.y - a.y) * t
703    };
704  }
705
706  function drawHexPath(ctx, cx, cy, radius) {
707    ctx.beginPath();
708
709    for (var i = 0; i < 6; i += 1) {
710      var angle = Math.PI / 3 * i;
711      var x = cx + Math.cos(angle) * radius;
712      var y = cy + Math.sin(angle) * radius;
713
714      if (i === 0) {
715        ctx.moveTo(x, y);
716      } else {
717        ctx.lineTo(x, y);
718      }
719    }
720
721    ctx.closePath();
722  }
723
724  function mountBackground(root) {
725    if (!root.classList.contains('zntech-neural-canvas--background')) {
726      return;
727    }
728
729    var mount = root.dataset.mount || 'elementor';
730    var host = null;
731
732    if (mount === 'parent') {
733      host = root.parentElement;
734    } else if (mount === 'elementor') {
735      host = root.closest('.e-con, .elementor-section, .elementor-top-section, .elementor-container');
736    }
737
738    if (!host || host === root || root.parentElement === host) {
739      if (host) {
740        host.classList.add('zntech-neural-canvas-host');
741      }
742      return;
743    }
744
745    host.classList.add('zntech-neural-canvas-host');
746    host.insertBefore(root, host.firstChild);
747  }
748
749  function readNumber(root, key, fallback) {
750    var value = parseInt(root.dataset[key], 10);
751    return Number.isFinite(value) ? value : fallback;
752  }
753
754  function readFloat(root, key, fallback) {
755    var value = parseFloat(root.dataset[key]);
756    return Number.isFinite(value) ? value : fallback;
757  }
758
759  function clamp(value, min, max) {
760    return Math.min(max, Math.max(min, value));
761  }
762
763  function readRgb(root, key, fallback) {
764    var value = root.dataset[key] || fallback;
765    return /^\d{1,3},\d{1,3},\d{1,3}$/.test(value) ? value : fallback;
766  }
767
768  function init() {
769    var roots = document.querySelectorAll('.zntech-neural-canvas');
770
771    roots.forEach(function (root) {
772      if (root.dataset.zntechNeuralReady === '1') {
773        return;
774      }
775
776      root.dataset.zntechNeuralReady = '1';
777      new NeuralCanvas(root);
778    });
779  }
780
781  if (document.readyState === 'loading') {
782    document.addEventListener('DOMContentLoaded', init);
783  } else {
784    init();
785  }
786})();

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.