🌳
pt0/peatsite/hamsaFlutterScriptAI.mjs
1export const hamsaFlutterScript = `
2if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
3 const wraps = document.querySelectorAll('.hamsa-flag-wrap')
4 if (wraps.length) {
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')
11 filter.id = id
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)
29 return {turb, disp}
30 }
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') + ')'
37 })
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
44 }, {passive: true})
45 const tick = () => {
46 vel *= 0.92
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)
52 }
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))
56 seedAccum += 0.02
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)
61 }
62 requestAnimationFrame(tick)
63 }