UNPKG

@scribehow/shaders

Version:

WebGL shaders used across various Scribe surfaces on web.

409 lines (325 loc) 24.3 kB
var Z=Object.defineProperty;var N=Object.getOwnPropertySymbols;var ee=Object.prototype.hasOwnProperty,te=Object.prototype.propertyIsEnumerable;var B=(e,t,i)=>t in e?Z(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,M=(e,t)=>{for(var i in t||(t={}))ee.call(t,i)&&B(e,i,t[i]);if(N)for(var i of N(t))te.call(t,i)&&B(e,i,t[i]);return e};var E=(e,t,i)=>new Promise((a,r)=>{var s=n=>{try{g(i.next(n))}catch(c){r(c)}},o=n=>{try{g(i.throw(n))}catch(c){r(c)}},g=n=>n.done?a(n.value):Promise.resolve(n.value).then(s,o);g((i=i.apply(e,t)).next())});var D=`precision mediump float; attribute vec2 uv; attribute vec2 position; varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 0, 1); } `;var _=`precision highp float; const float PI = 3.14159265359; const float WHITE_WAVE_TAIL_LENGTH = 2.0; const float COLORED_WAVE_NOISE_MULTIPLIER = 1.5; const float FALLOFF_CONVERSION_FACTOR = 8.0; const float ANIMATION_TIME_SCALE = 0.6; const float ANIMATION_SPATIAL_SCALE = 12.0; const float ANIMATION_SPATIAL_TIME_SCALE = 1.2; const float ANIMATION_WEIGHT_1 = 0.57; const float ANIMATION_WEIGHT_2 = 0.43; const float GRAIN_SCALE = 0.05; const float WAVE_PRESENCE_COLOR_FACTOR = 0.5; uniform vec4 uBackground; uniform vec2 uResolution; uniform float uTime; uniform float uWaveAmplitude; uniform float uWaveSpeed; uniform float uWaveFrequency; uniform int uWaveSide; uniform vec3 uWaveColor1; uniform vec3 uWaveColor2; uniform vec3 uWaveColor3; uniform vec3 uWaveColor4; uniform vec3 uWaveColor5; uniform float uMinFalloff; uniform float uMaxFalloff; uniform bool uEchoEnabled; uniform float uEchoOffset; uniform float uEchoRadius; uniform float uEchoFrequency; uniform float uEchoRingSize; uniform float uEchoStrokeOpacity; uniform float uEchoFillOpacity; varying vec2 vUv; // ===================================== // UTILITY FUNCTIONS // ===================================== float tanh(float x) { float e2x = exp(2.0 * x); return (e2x - 1.0) / (e2x + 1.0); } // 3D Perlin noise vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); } vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } float snoise(vec3 v) { const vec2 C = vec2(1.0/6.0, 1.0/3.0); const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); // First corner vec3 i = floor(v + dot(v, C.yyy)); vec3 x0 = v - i + dot(i, C.xxx); // Other corners vec3 g = step(x0.yzx, x0.xyz); vec3 l = 1.0 - g; vec3 i1 = min(g.xyz, l.zxy); vec3 i2 = max(g.xyz, l.zxy); vec3 x1 = x0 - i1 + C.xxx; vec3 x2 = x0 - i2 + C.yyy; vec3 x3 = x0 - D.yyy; // Permutations i = mod289(i); vec4 p = permute(permute(permute( i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0)); // Gradients: 7x7 points over a square, mapped onto an octahedron. // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) float n_ = 0.142857142857; // 1.0/7.0 vec3 ns = n_ * D.wyz - D.xzx; vec4 j = p - 49.0 * floor(p * ns.z * ns.z); vec4 x_ = floor(j * ns.z); vec4 y_ = floor(j - 7.0 * x_); vec4 x = x_ *ns.x + ns.yyyy; vec4 y = y_ *ns.x + ns.yyyy; vec4 h = 1.0 - abs(x) - abs(y); vec4 b0 = vec4(x.xy, y.xy); vec4 b1 = vec4(x.zw, y.zw); vec4 s0 = floor(b0)*2.0 + 1.0; vec4 s1 = floor(b1)*2.0 + 1.0; vec4 sh = -step(h, vec4(0.0)); vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy; vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww; vec3 p0 = vec3(a0.xy, h.x); vec3 p1 = vec3(a0.zw, h.y); vec3 p2 = vec3(a1.xy, h.z); vec3 p3 = vec3(a1.zw, h.w); // Normalise gradients vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3))); p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w; // Mix final noise value vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); m = m * m; return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3))); } // ===================================== // WAVE SIDE HELPER FUNCTIONS // ===================================== float getDistanceFromCenter(vec2 uv, int waveSide) { if (waveSide == 0 || waveSide == 1) { // Bottom or Top return abs(uv.x - 0.5) * 2.0; } else { // Right or Left return abs(uv.y - 0.5) * 2.0; } } float getRawDistanceFromCenter(vec2 uv, int waveSide) { if (waveSide == 0 || waveSide == 1) { // Bottom or Top return (uv.x - 0.5) * 2.0; } else { // Right or Left return (uv.y - 0.5) * 2.0; } } // ===================================== // ANIMATION FUNCTIONS // ===================================== float calculateAnimatedTailLength(vec2 uv, float time, float minFalloff, float maxFalloff) { float tailRange = (maxFalloff - minFalloff) * 0.5; float tailCenter = minFalloff + tailRange; return tailCenter + sin(time * ANIMATION_TIME_SCALE) * tailRange * ANIMATION_WEIGHT_1 + sin(uv.x * ANIMATION_SPATIAL_SCALE + time * ANIMATION_SPATIAL_TIME_SCALE) * tailRange * ANIMATION_WEIGHT_2; } float parabolicInterpolation(float rawDistance, float minVal, float maxVal) { float t = (rawDistance + 1.0) * 0.5; float t2 = t * t; return maxVal - (maxVal - minVal) * (4.0 * t - 4.0 * t2); } // ===================================== // WAVE GENERATION AND CALCULATION // ===================================== float generateWaveNoise(vec2 uv, float time, int waveIndex) { float spatialOffset = float(waveIndex) * 0.4 - 0.2; float temporalOffset = float(waveIndex) * 1.15; float amplitudeMultiplier = 1.0 + float(waveIndex) * 0.15; vec2 spatialVariation = vec2(spatialOffset, spatialOffset * 0.8); return snoise(vec3( (uv.x + spatialVariation.x) * uWaveFrequency + time * uWaveSpeed * 0.7, (uv.y + spatialVariation.y) * uWaveFrequency * 0.5 + time * uWaveSpeed * 0.3, time * 0.1 + temporalOffset )) * uWaveAmplitude * amplitudeMultiplier; } float calculateWaveIntensity(vec2 uv, float noise, float tailLength) { float basePosition; float baseHeight = uWaveAmplitude; float waveTop = baseHeight + noise; if (uWaveSide == 0) { // Bottom basePosition = uv.y; } else if (uWaveSide == 1) { // Top basePosition = 1.0 - uv.y; } else if (uWaveSide == 2) { // Right basePosition = 1.0 - uv.x; } else { // Left basePosition = uv.x; } float falloffDistance = (basePosition - waveTop) * 4.0; float falloffRate = FALLOFF_CONVERSION_FACTOR / tailLength; return max(0.0, 1.0 - tanh(falloffDistance * falloffRate * 0.5)); } // ===================================== // COLOR FUNCTIONS // ===================================== vec3 getWaveColor(vec2 uv) { float distanceFromCenter; vec2 noiseUv; if (uWaveSide == 0 || uWaveSide == 1) { // Bottom or Top distanceFromCenter = uv.x - 0.5; // Use horizontal noise orientation for horizontal wave sides noiseUv = vec2(uv.x, uv.y); } else { // Right or Left distanceFromCenter = uv.y - 0.5; // Rotate noise orientation for vertical wave sides to maintain consistency noiseUv = vec2(uv.y, uv.x); } // Generate noise to create irregular boundaries float noiseScale = 12.0; // Controls the frequency of the irregularity float noiseAmplitude = 0.005; // Controls the intensity of the irregularity float noise = snoise(vec3(noiseUv * noiseScale, uTime * 0.1)) * noiseAmplitude; // Apply noise to the distance, then take abs to maintain color symmetry float irregularDistance = abs(distanceFromCenter + noise); float t = irregularDistance * 2.0; // Simple blur approximation using 5 strategic samples (unrolled) vec3 color = vec3(0.0); // Blended color approach with overlapping contributions vec3 finalColor = vec3(0.0); float totalWeight = 0.0; // Color 5 contribution (center) float color5Weight = max(0.0, 1.0 - t / 0.2); color5Weight = smoothstep(0.0, 1.0, color5Weight); finalColor += uWaveColor5 * color5Weight; totalWeight += color5Weight; // Color 4 contribution float color4Weight = max(0.0, 1.0 - abs(t - 0.25) / 0.3); color4Weight = smoothstep(0.0, 1.0, color4Weight); finalColor += uWaveColor4 * color4Weight; totalWeight += color4Weight; // Color 3 contribution float color3Weight = max(0.0, 1.0 - abs(t - 0.45) / 0.3); color3Weight = smoothstep(0.0, 1.0, color3Weight); finalColor += uWaveColor3 * color3Weight; totalWeight += color3Weight; // Color 2 contribution float color2Weight = max(0.0, 1.0 - abs(t - 0.675) / 0.35); color2Weight = smoothstep(0.0, 1.0, color2Weight); finalColor += uWaveColor2 * color2Weight; totalWeight += color2Weight; // Color 1 contribution (outer) float color1Weight = max(0.0, (t - 0.6) / 0.4); color1Weight = smoothstep(0.0, 1.0, color1Weight); finalColor += uWaveColor1 * color1Weight; totalWeight += color1Weight; // Normalize by total weight to avoid oversaturation return finalColor / max(totalWeight, 0.001); } // ===================================== // ECHO FUNCTIONS // ===================================== vec2 getEchoCenter(int waveSide, float echoOffset) { if (waveSide == 0) { // Bottom return vec2(0.5, echoOffset); } else if (waveSide == 1) { // Top return vec2(0.5, 1.0 - echoOffset); } else if (waveSide == 2) { // Right return vec2(1.0 - echoOffset, 0.5); } else { // Left return vec2(echoOffset, 0.5); } } vec3 calculateEchoEffect(vec2 uv, vec2 center, vec2 resolution, float echoRadius, float echoFrequency, float echoRingSize, float echoStrokeOpacity, float echoFillOpacity) { float aspect = resolution.x / resolution.y; vec2 distVec = (uv - center) * vec2(aspect, 1.0); float d = length(distVec); float phase = (d - echoRadius) * echoFrequency; float ringWidthInUV = 0.001 * echoRingSize; float ringWidth = ringWidthInUV * echoFrequency; float ringPhase = mod(phase, 2.0 * PI); // Create crisp ring mask float ringMask = smoothstep(ringWidth, ringWidth * 0.5, abs(ringPhase - PI)); float angle = atan(distVec.y, distVec.x); float segmentCount = 1.0 + sin(d * 2.0) * 1.0; // 0-2 segments, varies by distance float segmentAngle = angle * segmentCount; // Add more variation to segment offset between rings float segmentOffset = phase * 0.33; float rotationSpeed = 0.1 + sin(d * 3.0) * 0.15; // Vary rotation speed by distance (slower) float rotatingAngle = segmentAngle + uTime * rotationSpeed + segmentOffset; float segmentPhase = mod(rotatingAngle, 2.0 * PI) / (2.0 * PI); float frontEdge = smoothstep(0.98, 1.0, segmentPhase); // Sharp front edge float trailingFade = smoothstep(0.0, 0.8, 1.0 - segmentPhase); // Long trailing fade float segmentMask = max(frontEdge, trailingFade); float baseIntensity = echoStrokeOpacity * exp(-d * 0.8); float shimmerTime = uTime * 2.0 + d * 5.0 + angle * 3.0; float shimmer = sin(shimmerTime) * 0.3 + 0.7; // Use wave color at current position with saturation and opacity controls vec3 baseColor = getWaveColor(uv); // Apply saturation boost float saturation = 1.2 + shimmer * 0.3; vec3 finalColor = baseColor * saturation; // Calculate ring effect (strokes) float ringEffect = ringMask * segmentMask * baseIntensity * shimmer; // Calculate fill effect (static fills) - smooth version float phaseProgress = (PI - phase) / (2.0 * PI); // Fill from inside out, max out at 3 float ringsToFill = floor(phaseProgress); float fillEffect = min(ringsToFill, 3.0) * echoFillOpacity; // Combine ring strokes and fills float totalEffect = ringEffect + fillEffect; return finalColor * totalEffect; } // ===================================== // MAIN FUNCTION // ===================================== void main() { // Input processing vec2 uv = vUv; vec2 resolution = uResolution; float time = uTime; // Wave noise generation float whiteWaveNoise = generateWaveNoise(uv, time, 0); float coloredWaveNoise = generateWaveNoise(uv, time, 1) * COLORED_WAVE_NOISE_MULTIPLIER; // Wave intensity calculation float whiteWaveIntensity = calculateWaveIntensity(uv, whiteWaveNoise, WHITE_WAVE_TAIL_LENGTH); // Colored wave with modulated falloff float distanceFromCenter = getDistanceFromCenter(uv, uWaveSide); float rawDistanceFromCenter = getRawDistanceFromCenter(uv, uWaveSide); float animatedTailLength = calculateAnimatedTailLength(uv, uTime, uMinFalloff, uMaxFalloff); float modulatedTailLength = parabolicInterpolation(rawDistanceFromCenter, uMinFalloff, uMaxFalloff) * animatedTailLength / (uMinFalloff + (uMaxFalloff - uMinFalloff) * 0.5); float coloredWaveIntensity = calculateWaveIntensity(uv, coloredWaveNoise, modulatedTailLength); // Base color composition vec4 color = uBackground; // Grain and wave presence calculation float grain = fract(sin(dot(uv * 1000.0, vec2(12.9898, 78.233))) * 43758.5453) * GRAIN_SCALE; float wavePresence = max(whiteWaveIntensity, coloredWaveIntensity * WAVE_PRESENCE_COLOR_FACTOR); // Final color composition vec3 positionColor = getWaveColor(uv); color.rgb = min(vec3(1.0), color.rgb + vec3(1.0) * whiteWaveIntensity + positionColor * coloredWaveIntensity + vec3(grain) * wavePresence); // Echo effect if (uEchoEnabled) { vec2 center = getEchoCenter(uWaveSide, uEchoOffset); // Render combined strokes and fills vec3 echoEffect = calculateEchoEffect(uv, center, resolution, uEchoRadius, uEchoFrequency, uEchoRingSize, uEchoStrokeOpacity, uEchoFillOpacity); // Add to final color color.rgb = min(vec3(1.0), color.rgb + echoEffect); // Ensure color values stay within valid range color.rgb = clamp(color.rgb, 0.0, 1.0); } gl_FragColor = color; } `;import{Camera as ce,Color as p,Program as se,Vec2 as R}from"ogl";import{RenderTarget as Ce,Geometry as ne,Mesh as ae}from"ogl";function A(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function k({canvas:e,renderer:t}){if(t){let i=t.gl,a=new Uint8Array(e.width*e.height*4);i.readPixels(0,0,e.width,e.height,i.RGBA,i.UNSIGNED_BYTE,a);let r=document.createElement("canvas");r.width=e.width,r.height=e.height;let s=r.getContext("2d");if(!s)return;s.fillStyle="#FFFFFF",s.fillRect(0,0,r.width,r.height);let o=new ImageData(new Uint8ClampedArray(a),e.width,e.height),g=new ImageData(new Uint8ClampedArray(o.data),o.width,o.height);for(let c=0;c<o.height;c++)for(let h=0;h<o.width;h++){let m=(c*o.width+h)*4,d=((o.height-1-c)*o.width+h)*4;g.data[d]=o.data[m],g.data[d+1]=o.data[m+1],g.data[d+2]=o.data[m+2],g.data[d+3]=o.data[m+3]}s.putImageData(g,0,0);let n=document.createElement("a");n.download="fluid-light.png",r.toBlob(c=>{c&&(n.href=URL.createObjectURL(c),n.click(),URL.revokeObjectURL(n.href))},"image/png")}else{let i=document.createElement("a");i.download="fluid-light.png",e.toBlob(a=>{a&&(i.href=URL.createObjectURL(a),i.click(),URL.revokeObjectURL(i.href))},"image/png")}}function z(e,t,i){return new ae(e,{geometry:t,program:i})}function G(e){return new ne(e,{position:{size:2,data:new Float32Array([-1,-1,3,-1,-1,3])},uv:{size:2,data:new Float32Array([0,0,2,0,0,2])}})}function T(e,t){let i=M({},e);for(let a in t)t[a]!==void 0&&(typeof t[a]=="object"&&t[a]!==null&&!Array.isArray(t[a])?i[a]=T(i[a]||{},t[a]):i[a]=t[a]);return i}import{Renderer as re}from"ogl";function le(e,t="Shader"){if(!e)throw new Error(`${t}: Canvas parameter is required and cannot be null or undefined`);if(!(e instanceof HTMLCanvasElement))throw new Error(`${t}: Canvas parameter must be an HTMLCanvasElement`);if(!e.isConnected)throw new Error(`${t}: Canvas must be attached to the DOM before creating shader`);if(!e.clientWidth||!e.clientHeight)throw new Error(`${t}: Canvas must have non-zero dimensions (width and height)`);if(typeof window!="undefined"){let i=document.createElement("canvas"),a=null;try{a=i.getContext("webgl2")||i.getContext("webgl")||i.getContext("experimental-webgl")}catch(r){throw new Error(`${t}: Failed to get WebGL context - WebGL may not be supported`)}if(!a)throw new Error(`${t}: WebGL is not supported in this browser`);i.remove()}}function U(e,t="Shader"){le(e,t);let i,a;try{i=new re({dpr:devicePixelRatio,canvas:e,width:e.clientWidth,height:e.clientHeight}),a=i.gl}catch(r){throw new Error(`${t}: Failed to create WebGL renderer - ${r instanceof Error?r.message:"Unknown error"}`)}if(!a)throw new Error(`${t}: Failed to get WebGL context from renderer`);return a.clearColor(1,1,1,0),{renderer:i,gl:a}}var ue={side:0,background:"#020617",lightWave:{amplitude:.04,speed:.4,frequency:1.2,color1:"#5648FB",color2:"#3AC3FF",color3:"#CE7FFF",color4:"#F45397",color5:"#FFB525",minFalloff:6,maxFalloff:8},echo:{enabled:!0,offset:.1,radius:.9,frequency:20,ringSize:2,strokeOpacity:.2,fillOpacity:.01,animateIn:!0,animationDuration:3e3,animationDelay:200}};function q(i){return E(this,arguments,function*(e,t={}){let{renderer:a,gl:r}=U(e,"FluidLight"),s=T(ue,t),o=JSON.parse(JSON.stringify({side:s.side,background:s.background,lightWave:s.lightWave,echo:s.echo})),g=JSON.parse(JSON.stringify(o)),n=new R,c=new ce(r,{fov:35});c.position.set(0,0,5);let h=new R,m=new R,d=performance.now();function C(){var v,W;let f=(W=(v=e.parentElement)==null?void 0:v.clientWidth)!=null?W:e.clientWidth,u=e.clientHeight;a.setSize(f,u),c.perspective({aspect:f/u}),n.set(f,u)}function x(f){let u=e.getBoundingClientRect();h.x=(f.clientX-u.left)/u.width*2-1,h.y=-((f.clientY-u.top)/u.height)*2+1}function F(f){let u=e.getBoundingClientRect(),v=f.touches[0];h.x=(v.clientX-u.left)/u.width*2-1,h.y=-((v.clientY-u.top)/u.height)*2+1}e.addEventListener("mousemove",x),e.addEventListener("touchmove",F),C();let l=fe(r,o,n),S=G(r),w=z(r,S,l),y;function P(f){y=requestAnimationFrame(P),C(),H(f),a.render({scene:w,camera:c})}function H(f){let u=new p(o.background);l.uniforms.uBackground.value=[u.r,u.g,u.b,1],l.uniforms.uTime.value=f*.001,l.uniforms.uResolution.value=n,l.uniforms.uWaveAmplitude.value=o.lightWave.amplitude,l.uniforms.uWaveSpeed.value=o.lightWave.speed,l.uniforms.uWaveFrequency.value=o.lightWave.frequency,l.uniforms.uWaveSide.value=o.side,l.uniforms.uWaveColor1.value=new p(o.lightWave.color1),l.uniforms.uWaveColor2.value=new p(o.lightWave.color2),l.uniforms.uWaveColor3.value=new p(o.lightWave.color3),l.uniforms.uWaveColor4.value=new p(o.lightWave.color4),l.uniforms.uWaveColor5.value=new p(o.lightWave.color5),l.uniforms.uMinFalloff.value=o.lightWave.minFalloff,l.uniforms.uMaxFalloff.value=o.lightWave.maxFalloff,l.uniforms.uEchoEnabled.value=o.echo.enabled,l.uniforms.uEchoOffset.value=o.echo.offset,l.uniforms.uEchoRadius.value=o.echo.radius,l.uniforms.uEchoFrequency.value=j(f),l.uniforms.uEchoRingSize.value=o.echo.ringSize,l.uniforms.uEchoStrokeOpacity.value=J(f),l.uniforms.uEchoFillOpacity.value=o.echo.fillOpacity}function j(f){if(!o.echo.animateIn)return o.echo.frequency;let u=f-d,v=o.echo.animationDelay,W=o.echo.animationDuration;if(u<v)return 0;let O=Math.min((u-v)/W,1),I=A(O);return o.echo.frequency*I}function J(f){if(!o.echo.animateIn)return o.echo.strokeOpacity;let u=f-d,v=o.echo.animationDelay,W=o.echo.animationDuration;if(u<v)return 0;let O=Math.min((u-v)/W,1),I=A(O);return o.echo.strokeOpacity*I}function $(f){Object.assign(o,f)}function Y(){Object.assign(o,JSON.parse(JSON.stringify(g))),d=performance.now()}function X(){return JSON.parse(JSON.stringify(o))}function K(){e.removeEventListener("mousemove",x),e.removeEventListener("touchmove",F),cancelAnimationFrame(y)}function Q(){return o}return P(performance.now()),{updateParams:$,reset:Y,exportConfig:X,destroy:K,getParams:Q}})}function fe(e,t,i){return new se(e,{vertex:D,fragment:_,uniforms:{uBackground:{value:[0,0,0,1]},uResolution:{value:i},uTime:{value:0},uWaveAmplitude:{value:t.lightWave.amplitude},uWaveSpeed:{value:t.lightWave.speed},uWaveFrequency:{value:t.lightWave.frequency},uWaveSide:{value:t.side},uWaveColor1:{value:new p(t.lightWave.color1)},uWaveColor2:{value:new p(t.lightWave.color2)},uWaveColor3:{value:new p(t.lightWave.color3)},uWaveColor4:{value:new p(t.lightWave.color4)},uWaveColor5:{value:new p(t.lightWave.color5)},uMinFalloff:{value:t.lightWave.minFalloff},uMaxFalloff:{value:t.lightWave.maxFalloff},uEchoEnabled:{value:t.echo.enabled},uEchoOffset:{value:t.echo.offset},uEchoRadius:{value:t.echo.radius},uEchoFrequency:{value:t.echo.frequency},uEchoRingSize:{value:t.echo.ringSize},uEchoStrokeOpacity:{value:t.echo.strokeOpacity},uEchoFillOpacity:{value:t.echo.fillOpacity}}})}function V(e){return E(this,null,function*(){let[{Pane:t},i]=yield Promise.all([import("tweakpane"),import("@tweakpane/plugin-essentials")]),a=e.getParams(),{lightWave:r,echo:s}=a,o=new t({title:"Fluid Light",expanded:!0});o.registerPlugin(i);let g=o.addBlade({view:"fpsgraph",rows:2}),n=o.addTab({pages:[{title:"Parameters"},{title:"Advanced"}]});n.pages[0].addBinding(a,"side",{label:"Side",options:{Bottom:0,Top:1,Right:2,Left:3}}),n.pages[0].addBinding(a,"background",{label:"Background"}),n.pages[0].addBinding(r,"amplitude",{label:"Amplitude",min:.01,max:.2,step:.01}),n.pages[0].addBinding(r,"speed",{label:"Speed",min:.01,max:1,step:.01}),n.pages[0].addBinding(r,"frequency",{label:"Frequency",min:.01,max:2,step:.01}),n.pages[0].addBinding(r,"color1",{label:"Color 1"}),n.pages[0].addBinding(r,"color2",{label:"Color 2"}),n.pages[0].addBinding(r,"color3",{label:"Color 3"}),n.pages[0].addBinding(r,"color4",{label:"Color 4"}),n.pages[0].addBinding(r,"color5",{label:"Color 5"}),n.pages[0].addBinding(r,"minFalloff",{label:"Min Falloff",min:1,max:10,step:.01}),n.pages[0].addBinding(r,"maxFalloff",{label:"Max Falloff",min:1,max:10,step:.01});let c=n.pages[0].addFolder({title:"Echo",expanded:!1});c.addBinding(s,"enabled",{label:"Enabled"}),c.addBinding(s,"offset",{label:"Offset",min:0,max:.5,step:.01}),c.addBinding(s,"radius",{label:"Radius",min:.01,max:1,step:.01}),c.addBinding(s,"frequency",{label:"Frequency",min:1,max:40,step:.5}),c.addBinding(s,"ringSize",{label:"Thickness",min:.1,max:4,step:.1}),c.addBinding(s,"strokeOpacity",{label:"Stroke Opacity",min:0,max:1,step:.01}),c.addBinding(s,"fillOpacity",{label:"Fill Opacity",min:0,max:.1,step:.01});let h=c.addFolder({title:"Animation",expanded:!1}),m=performance.now();return h.addBinding(s,"animateIn",{label:"Enabled"}).on("change",()=>{m=performance.now()}),s.animateIn&&(h.addBinding(s,"animationDuration",{label:"Duration",min:500,max:5e3,step:100}).on("change",()=>{m=performance.now()}),h.addBinding(s,"animationDelay",{label:"Delay",min:0,max:2e3,step:100}).on("change",()=>{m=performance.now()})),n.pages[1].addButton({title:"Reset"}).on("click",()=>{e.reset(),o.refresh(),m=performance.now()}),n.pages[1].addButton({title:"Export Config"}).on("click",()=>{let d=JSON.stringify(o.exportState(),null,2),C=new Blob([d],{type:"application/json"}),x=document.createElement("a");x.download="fluid-light-config.json",x.href=URL.createObjectURL(C),x.click(),URL.revokeObjectURL(x.href)}),n.pages[1].addButton({title:"Import Config"}).on("click",()=>{let d=document.createElement("input");d.type="file",d.accept=".json",d.onchange=C=>{var F;let x=(F=C.target.files)==null?void 0:F[0];if(x){let l=new FileReader;l.onload=S=>{var w;try{let y=JSON.parse((w=S.target)==null?void 0:w.result);o.importState(y),m=performance.now()}catch(y){console.error("Failed to import config:",y),alert("Failed to import config. Please check the file format.")}},l.readAsText(x)}},d.click()}),n.pages[1].addButton({title:"Download PNG"}).on("click",()=>{let d=document.querySelector("canvas");d&&k({canvas:d,renderer:null})}),o.on("change",()=>{e.updateParams(a)}),{pane:o,fpsGraph:g,dispose:()=>{o.dispose()}}})}var de={create:q,attachDevtools:V},ge=de;export{ge as FluidLight}; //# sourceMappingURL=index.js.map