1export const hamsaFlutterScript = ` 2if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) { 3 const wraps = document.querySelectorAll('.hamsa-flag-wrap') 5 const svgNS = 'http://www.w3.org/2000/svg' 6 const svg = document.createElementNS(svgNS, 'svg') 7 svg.setAttribute('width', '0'); svg.setAttribute('height', '0') 8 svg.style.cssText = 'position:absolute' 9 const mkFilter = (id, seedBase, freq) => { 10 const filter = document.createElementNS(svgNS, 'filter') 12 filter.setAttribute('x', '-20%'); filter.setAttribute('y', '-20%') 13 filter.setAttribute('width', '140%'); filter.setAttribute('height', '140%') 14 const turb = document.createElementNS(svgNS, 'feTurbulence') 15 turb.setAttribute('type', 'fractalNoise') 16 turb.setAttribute('baseFrequency', freq) 17 turb.setAttribute('numOctaves', '2'); turb.setAttribute('seed', String(seedBase)) 18 turb.setAttribute('result', 'noise') 19 const disp = document.createElementNS(svgNS, 'feDisplacementMap') 20 disp.setAttribute('in', 'SourceGraphic'); disp.setAttribute('in2', 'noise') 21 disp.setAttribute('scale', '0') 22 disp.setAttribute('xChannelSelector', 'R'); disp.setAttribute('yChannelSelector', 'G') 23 filter.appendChild(turb); filter.appendChild(disp) 24 const drop = document.createElementNS(svgNS, 'feDropShadow') 25 drop.setAttribute('dx', '0'); drop.setAttribute('dy', '2') 26 drop.setAttribute('stdDeviation', '2'); drop.setAttribute('flood-opacity', '0.5') 27 filter.appendChild(drop) 28 svg.appendChild(filter) 31 const filterA = mkFilter('hamsaFlutterA', 3, '0.02 0.05') 32 const filterB = mkFilter('hamsaFlutterB', 53, '0.04 0.08') 33 document.body.appendChild(svg) 34 wraps.forEach((wrap, i) => { 35 const flag = wrap.querySelector('.hamsa-flag') 36 if (flag) flag.style.filter = 'url(#hamsaFlutter' + (i === 0 ? 'A' : 'B') + ')' 38 let vel = 0, lastY = scrollY, lastT = performance.now(), seedAccum = 3 39 addEventListener('scroll', () => { 40 const now = performance.now(), dt = Math.max(1, now - lastT) 41 vel += (scrollY - lastY) / dt * 16 42 vel = Math.max(-40, Math.min(40, vel)) 43 lastY = scrollY; lastT = now 47 if (Math.abs(vel) < 0.1) vel = 0 48 const velAbs = Math.abs(vel).toFixed(2) 49 for (const wrap of wraps) { 50 wrap.style.setProperty('--vel', vel.toFixed(2)) 51 wrap.style.setProperty('--vel-abs', velAbs) 53 const baseDispScale = Math.min(10, parseFloat(velAbs) * 0.25) 54 filterA.disp.setAttribute('scale', (baseDispScale * parseFloat(getComputedStyle(wraps[0]).getPropertyValue('--vel-mult') || 1)).toFixed(1)) 55 filterB.disp.setAttribute('scale', (baseDispScale * parseFloat(getComputedStyle(wraps[1]).getPropertyValue('--vel-mult') || 1)).toFixed(1)) 57 const seed = (seedAccum % 100).toFixed(2) 58 filterA.turb.setAttribute('seed', seed) 59 filterB.turb.setAttribute('seed', ((parseFloat(seed) + 50) % 100).toFixed(2)) 60 requestAnimationFrame(tick) 62 requestAnimationFrame(tick)