1function ready() { 2 3 const unlimitedRadio = document.querySelector('#pricing-unlimited'); 4 const unlimitedPackage = document.querySelector('.pricing__package--unlimited'); 5 6 if(!unlimitedRadio || !unlimitedPackage || matchMedia('(prefers-reduced-motion: reduce)').matches) return; 7 8 const random = (min, max) => min + Math.random() * (max - min); 9 10 const visibleCenter = element => { 11 12 if(!element) return null; 13 14 const rect = element.getBoundingClientRect(); 15 16 const left = Math.max(rect.left, 0); 17 const right = Math.min(rect.right, innerWidth); 18 const top = Math.max(rect.top, 0); 19 const bottom = Math.min(rect.bottom, innerHeight); 20 21 if(right <= left || bottom <= top) return null; 22 23 return { x: (left + right) / 2, y: (top + bottom) / 2 }; 24 25 }; 26 27 const confettiBurst = 90; 28 const confettiLimit = 360; 29 const confettiColors = ['blue', 'green', 'orange', 'red', 'yellow']; 30 const confettiParticles = []; 31 const confettiVisibility = 0.5; 32 33 let confettiContainer = null; 34 let confettiObserver = null; 35 let confettiRunning = false; 36 let confettiLastTime = 0; 37 38 const watchConfetti = () => { 39 40 if(confettiObserver) confettiObserver.disconnect(); 41 42 confettiObserver = new IntersectionObserver(entries => { 43 44 const entry = entries[entries.length - 1]; 45 46 if(!unlimitedRadio.checked) return; 47 48 if(entry.intersectionRatio < confettiVisibility && entry.intersectionRect.height < innerHeight * confettiVisibility) return; 49 50 confettiObserver.disconnect(); 51 52 confettiObserver = null; 53 54 burstConfetti(visibleCenter(unlimitedPackage) || { x: innerWidth / 2, y: innerHeight / 2 }); 55 56 }, { threshold: [0, 0.25, 0.5, 0.75, 1] }); 57 58 confettiObserver.observe(unlimitedPackage); 59 60 }; 61 62 const burstConfetti = confettiOrigin => { 63 64 if(!confettiContainer) { 65 66 confettiContainer = document.createElement('div'); 67 68 confettiContainer.className = 'pricing__confetti'; 69 confettiContainer.setAttribute('aria-hidden', 'true'); 70 71 document.body.appendChild(confettiContainer); 72 73 } 74 75 for(const particle of confettiParticles.splice(0, confettiParticles.length + confettiBurst - confettiLimit)) { 76 77 particle.element.remove(); 78 79 } 80 81 for(let i = 0; i < confettiBurst; i++) { 82 83 const width = random(2, 10); 84 const height = random(2, 4); 85 const dx = random(-0.6, 0.6); 86 const dy = random(-1, -0.3); 87 const length = Math.hypot(dx, dy); 88 const color = confettiColors[Math.floor(Math.random() * confettiColors.length)]; 89 90 const particleElement = document.createElement('i'); 91 92 particleElement.style.cssText = `width:${width}px;height:${height}px;background:var(--color-${color});border-radius:${Math.random() > 0.5 ? '50%' : '1px'}`; 93 94 confettiContainer.appendChild(particleElement); 95 96 confettiParticles.push({ 97 element: particleElement, width, height, 98 x: confettiOrigin.x, 99 y: confettiOrigin.y, 100 vx: dx / length * random(150, 750), 101 vy: dy / length * random(300, 800), 102 rotation: random(0, 360), 103 rotationSpeed: random(-250, 250) 104 }); 105 106 } 107 108 if(!confettiRunning) { 109 110 confettiRunning = true; 111 confettiLastTime = 0; 112 113 requestAnimationFrame(confettiLoop); 114 115 } 116 117 }; 118 119 unlimitedRadio.addEventListener('change', () => { 120 121 if(!unlimitedRadio.checked) return; 122 123 watchConfetti(); 124 125 }); 126 127 if(unlimitedRadio.checked) unlimitedRadio.dispatchEvent(new Event('change')); 128 129 function confettiLoop(time) { 130 131 const delta = confettiLastTime ? Math.min((time - confettiLastTime) / 1000, 0.064) : 0; 132 const damping = 0.98 ** (delta * 60); 133 const viewportFloor = innerHeight; 134 135 confettiLastTime = time; 136 137 for(let i = confettiParticles.length - 1; i >= 0; i--) { 138 139 const particle = confettiParticles[i]; 140 141 particle.vx = (particle.vx + random(-350, 350) * delta) * damping; 142 particle.vy = (particle.vy + 180 * particle.height * delta) * damping; 143 particle.x += particle.vx * delta; 144 particle.y += particle.vy * delta; 145 particle.rotation += particle.rotationSpeed * delta; 146 147 if(particle.vy > 0 && particle.y > viewportFloor + Math.max(particle.width, particle.height) * 2) { 148 149 particle.element.remove(); 150 151 confettiParticles.splice(i, 1); 152 153 } else { 154 155 particle.element.style.transform = `translate3d(${particle.x}px, ${particle.y}px, 0) rotate(${particle.rotation}deg)`; 156 157 } 158 159 } 160 161 if(confettiParticles.length) { 162 163 requestAnimationFrame(confettiLoop); 164 165 } else { 166 167 confettiRunning = false;
168 169 confettiContainer.remove(); 170 171 confettiContainer = null; 172 173 } 174 175 } 176 177} 178 179export { ready };
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.