1/* Pas âBranżeâ v2: flow speed per word wire from its own length (same pace in em/s at any width); sparks spawned on 2 every conductor at random intervals and speeds, and every 7 to 11 s a surge (one fast spark per conductor, 45 ms 3 apart, a diagonal front across the bundle); the word a spark passes behind lights up. 4 Cost on phones: positions are computed from the animations' clocks and the layout measured once (no 5 getBoundingClientRect per frame), classes change only when a word's state changes, everything runs only while the 6 band is on screen and the page is visible; nothing under reduced motion. */ 7(function () { 8 var PACE = [2.1, 2.5, 2.3]; // em per second, per word wire 9 var SPEED = [900, 1700]; // spark px per second, min and max 10 var GAP = [350, 2600]; // ms between sparks on one conductor 11 var SURGE = [7000, 11000]; // ms between surges 12 // data-rhythm="3f": no chance involved. The three word wires are phases L1, L2, L3 and fire 120° apart, one spark 13 // each per period at one speed, an even beat with no gaps; the four other conductors stay quiet like N and PE. 14 // (A surge every 4th period was tried and dropped, PaweÅ 2026-09-30: three sparks in a row, then a gap, looked odd.) 15 var PERIOD = 1800, PHASE_SPEED = 1250; 16 var reduce = window.matchMedia('(prefers-reduced-motion: reduce)'); 17 var rnd = function (a) { return a[0] + Math.random() * (a[1] - a[0]); }; 18 document.querySelectorAll('.pcs-branze').forEach(function (band) { 19 // the HTML holds each list once; the copy that closes the loop is added here, and not at all under reduced motion 20 if (!reduce.matches) band.querySelectorAll('.pcs-branze__track').forEach(function (t) { 21 var c = t.cloneNode(true); c.setAttribute('aria-hidden', 'true'); t.parentNode.insertBefore(c, t.nextSibling); 22 }); 23 var wires = [].slice.call(band.querySelectorAll('.pcs-branze__wire')).map(function (el) { 24 return { el: el, track: el.querySelector('.pcs-branze__track'), 25 items: [].slice.call(el.querySelectorAll('li')).map(function (li) { return { el: li, x: 0, w: 0, live: false }; }), 26 sparks: [], timer: 0, width: 0, trackW: 0, flow: null }; 27 }); 28 var words = wires.filter(function (w) { return w.items.length; }); 29 var on = false, raf = 0, surgeTimer = 0, phaseTimers = []; 30 var threePhase = band.getAttribute('data-rhythm') === '3f'; 31 32 function size() { // layout read once per resize, not per frame 33 wires.forEach(function (w) { w.width = w.el.clientWidth; }); 34 words.forEach(function (w, i) { 35 var fs = parseFloat(getComputedStyle(w.items[0].el).fontSize); 36 w.trackW = w.track.offsetWidth; 37 w.items.forEach(function (it) { it.x = it.el.offsetLeft; it.w = it.el.offsetWidth; }); // offsets ignore transforms 38 w.el.style.setProperty('--branze-flow-dur', (w.trackW / (fs * PACE[i % PACE.length])).toFixed(1) + 's'); 39 w.flow = null; // the CSS animation restarts its timing; picked up again in tick() 40 }); 41 } 42 function spark(w, speed) { 43 var s = document.createElement('span'); 44 s.className = 'pcs-branze__spark'; s.setAttribute('aria-hidden', 'true'); 45 s.innerHTML = '<i></i><i></i><i></i>'; 46 w.el.appendChild(s); 47 var from = w.width, len = s.offsetWidth, head = s.firstChild.offsetWidth; 48 var dur = (from + len) / (speed || rnd(SPEED)) * 1000; 49 var a = s.animate([{ transform: 'translate(' + from + 'px, -50%)' }, { transform: 'translate(' + (-len) + 'px, -50%)' }], 50 { duration: dur, easing: 'linear' }); 51 var rec = { anim: a, from: from, to: -len, dur: dur, head: head }; 52 w.sparks.push(rec); 53 a.onfinish = function () { s.remove(); w.sparks.splice(w.sparks.indexOf(rec), 1); }; 54 } 55 function schedule(w, first) { 56 w.timer = setTimeout(function () { 57 if (!on) return; 58 spark(w); 59 if (Math.random() < .18) setTimeout(function () { if (on) spark(w); }, 90); 60 schedule(w); 61 }, first ? Math.random() * GAP[1] : rnd(GAP)); 62 } 63 function surge(first) { 64 surgeTimer = setTimeout(function () { 65 if (!on) return;
66 wires.forEach(function (w, k) { setTimeout(function () { if (on) spark(w, SPEED[1]); }, k * 45); }); 67 surge(); 68 }, first ? 2500 : rnd(SURGE)); 69 } 70 function phases() { // one period: L1, L2, L3 at 0, 1/3, 2/3 71 if (!on) return; 72 words.forEach(function (w, p) { phaseTimers.push(setTimeout(function () { if (on) spark(w, PHASE_SPEED); }, p * PERIOD / 3)); }); 73 phaseTimers.push(setTimeout(phases, PERIOD)); 74 } 75 function tick() { 76 words.forEach(function (w) { 77 if (!w.flow && w.track.getAnimations) w.flow = w.track.getAnimations()[0] || null; 78 if (!w.flow) return; 79 var d = w.flow.effect.getTiming().duration, t = w.flow.currentTime || 0; 80 var shift = -((t % d) / d) * w.trackW; 81 var heads = w.sparks.map(function (r) { 82 var x = r.from + (r.to - r.from) * Math.min(1, (r.anim.currentTime || 0) / r.dur); 83 return [x, x + r.head]; 84 }); 85 w.items.forEach(function (it) { 86 var l = it.x + shift, rgt = l + it.w, live = false; 87 if (rgt > 0 && l < w.width) { 88 for (var k = 0; k < heads.length && !live; k++) live = l < heads[k][1] && rgt > heads[k][0]; 89 } 90 if (live !== it.live) { it.live = live; it.el.classList.toggle('is-live', live); } 91 }); 92 }); 93 raf = on ? requestAnimationFrame(tick) : 0; 94 } 95 function start() { 96 if (on || reduce.matches || document.hidden) return; 97 on = true; 98 if (threePhase) phases(); else { wires.forEach(function (w) { schedule(w, true); }); surge(true); } 99 raf = requestAnimationFrame(tick); 100 } 101 function stop() { 102 on = false; if (raf) cancelAnimationFrame(raf); raf = 0; 103 wires.forEach(function (w) { clearTimeout(w.timer); }); clearTimeout(surgeTimer); 104 phaseTimers.forEach(clearTimeout); phaseTimers = []; 105 } 106 var visible = false; 107 size(); 108 if (document.fonts && document.fonts.ready) document.fonts.ready.then(size); 109 window.addEventListener('resize', size); 110 new IntersectionObserver(function (e) { visible = e[0].isIntersecting; visible ? start() : stop(); }).observe(band); 111 document.addEventListener('visibilitychange', function () { document.hidden ? stop() : visible && start(); }); 112 reduce.addEventListener('change', function () { reduce.matches ? stop() : visible && start(); }); 113 }); 114})();
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.