1/** 2 * ambient-fx.js â Universal Background Particles & Fluid Magnetic Cursor 3 * By: Masoud Abdullah (msaood.com) 4 */ 5(function () { 6 // 1. ØÙÙ Ø§ÙØªÙسÙÙØ§Øª Ø§ÙØ®Ø§ØµØ© Ø¨Ø§ÙØªØ£Ø«Ùرات تÙÙØ§Ø¦Ùا٠7 const style = document.createElement('style'); 8 style.id = 'ambient-fx-styles'; 9 style.textContent = ` 10 #ambientCanvas { 11 position: fixed; 12 inset: 0; 13 pointer-events: none; 14 z-index: 0; 15 opacity: 0.45; 16 } 17 .custom-cursor { 18 position: fixed; 19 top: 0; 20 left: 0; 21 width: 24px; 22 height: 24px; 23 border: 1.5px solid var(--red-soft, #ff4444); 24 border-radius: 50%; 25 pointer-events: none; 26 z-index: 99999; 27 transform: translate(-50%, -50%); 28 transition: width 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), 29 height 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), 30 background-color 0.2s ease, 31 border-color 0.2s ease; 32 mix-blend-mode: difference; 33 } 34 .custom-cursor-dot { 35 position: fixed; 36 top: 0; 37 left: 0; 38 width: 5px; 39 height: 5px; 40 background-color: var(--red-soft, #ff4444); 41 border-radius: 50%; 42 pointer-events: none; 43 z-index: 100000; 44 transform: translate(-50%, -50%); 45 } 46 .custom-cursor.cursor-hover { 47 width: 48px; 48 height: 48px; 49 background-color: rgba(255, 68, 68, 0.2); 50 border-color: var(--red-soft, #ff4444); 51 } 52 @media (max-width: 900px) { 53 .custom-cursor, .custom-cursor-dot { 54 display: none !important; 55 } 56 } 57 `; 58 document.head.appendChild(style); 59 60 // 2. تشغÙÙ Ø§ÙØªØ£Ø«Ùرات Ø¹ÙØ¯ تØÙ ÙÙ Ù ØØªÙÙ Ø§ÙØµÙØØ© 61 function initAmbientFX() { 62 // ØÙÙ ÙØ§ÙÙØ§Ø³ Ø§ÙØ®ÙÙÙØ© 63 if (!document.getElementById('ambientCanvas')) { 64 const canvas = document.createElement('canvas'); 65 canvas.id = 'ambientCanvas'; 66 document.body.prepend(canvas); 67 initParticleMesh(canvas); 68 } 69 70 // ØÙ٠اÙ٠ؤشر Ø§ÙØªÙاعÙÙ ÙØ£Ø¬Ùزة Ø³Ø·Ø Ø§ÙÙ ÙØªØ¨ 71 if (window.innerWidth > 900 && !document.getElementById('customCursor')) { 72 const cursor = document.createElement('div'); 73 cursor.className = 'custom-cursor'; 74 cursor.id = 'customCursor'; 75 76 const dot = document.createElement('div'); 77 dot.className = 'custom-cursor-dot'; 78 dot.id = 'customCursorDot'; 79 80 document.body.appendChild(cursor); 81 document.body.appendChild(dot); 82 initFluidCursor(cursor, dot); 83 } 84 } 85 86 // Ù ØØ±Ù Ø±Ø³Ù Ø´Ø¨ÙØ© Ø§ÙØ¬Ø²Ùئات (Particle Mesh Engine) 87 function initParticleMesh(canvas) { 88 const ctx = canvas.getContext('2d'); 89 let width = (canvas.width = window.innerWidth); 90 let height = (canvas.height = window.innerHeight); 91 92 window.addEventListener('resize', () => { 93 width = canvas.width = window.innerWidth; 94 height = canvas.height = window.innerHeight; 95 }, { passive: true }); 96 97 const particles = []; 98 const count = Math.min(Math.floor(window.innerWidth / 35), 35); 99 100 for (let i = 0; i < count; i++) { 101 particles.push({ 102 x: Math.random() * width, 103 y: Math.random() * height, 104 vx: (Math.random() - 0.5) * 0.35, 105 vy: (Math.random() - 0.5) * 0.35, 106 radius: Math.random() * 1.8 + 1 107 }); 108 } 109 110 function render() { 111 ctx.clearRect(0, 0, width, height); 112 ctx.fillStyle = 'rgba(17, 18, 15, 0.15)'; 113 ctx.strokeStyle = 'rgba(17, 18, 15, 0.05)'; 114 115 for (let i = 0; i < particles.length; i++) { 116 const p = particles[i]; 117 p.x += p.vx; 118 p.y += p.vy; 119 120 if (p.x < 0) p.x = width; 121 if (p.x > width) p.x = 0; 122 if (p.y < 0) p.y = height; 123 if (p.y > height) p.y = 0; 124 125 ctx.beginPath(); 126 ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); 127 ctx.fill(); 128 129 for (let j = i + 1; j < particles.length; j++) { 130 const p2 = particles[j]; 131 const dist = Math.hypot(p.x - p2.x, p.y - p2.y); 132 if (dist < 110) { 133 ctx.lineWidth = 1 - dist / 110; 134 ctx.beginPath(); 135 ctx.moveTo(p.x, p.y); 136 ctx.lineTo(p2.x, p2.y); 137 ctx.stroke(); 138 } 139 } 140 } 141 requestAnimationFrame(render); 142 } 143 render(); 144 } 145 146 // Ù ØØ±Ù ٠ؤشر اÙÙØ£Ø±Ø© اÙÙ ØºÙØ§Ø·Ùس٠(Fluid Cursor) 147 function initFluidCursor(cursor, dot) { 148 let mouseX = window.innerWidth / 2; 149 let mouseY = window.innerHeight / 2;
150 let cursorX = mouseX; 151 let cursorY = mouseY; 152 153 window.addEventListener('mousemove', (e) => { 154 mouseX = e.clientX; 155 mouseY = e.clientY; 156 dot.style.left = `${mouseX}px`; 157 dot.style.top = `${mouseY}px`; 158 }, { passive: true }); 159 160 function render() { 161 cursorX += (mouseX - cursorX) * 0.2; 162 cursorY += (mouseY - cursorY) * 0.2; 163 cursor.style.left = `${cursorX}px`; 164 cursor.style.top = `${cursorY}px`; 165 requestAnimationFrame(render); 166 } 167 render(); 168 169 // Ø¥Ø¶Ø§ÙØ© ØªØ£Ø«ÙØ± Ø§ÙØªÙØ¨ÙØ± Ø¹ÙØ¯ Ø§ÙØªÙ Ø±ÙØ± عÙÙ Ø§ÙØ¹Ùاصر Ø§ÙØªÙاعÙÙØ© 170 const interactiveSelectors = 'a, button, input, textarea, select, .interactive-el, [role="button"]'; 171 172 document.addEventListener('mouseover', (e) => { 173 if (e.target.closest(interactiveSelectors)) { 174 cursor.classList.add('cursor-hover'); 175 } 176 }); 177 178 document.addEventListener('mouseout', (e) => { 179 if (e.target.closest(interactiveSelectors)) { 180 cursor.classList.remove('cursor-hover'); 181 } 182 }); 183 } 184 185 if (document.readyState === 'loading') { 186 document.addEventListener('DOMContentLoaded', initAmbientFX); 187 } else { 188 initAmbientFX(); 189 } 190})();
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.