{"version":3,"file":"Ripple.DHYTKAzD.mjs","names":[],"sources":["https:/framerusercontent.com/modules/pqwKpQqmNWYWn7DDyG4C/hdGSAmMyFuoXz3bSv42I/Ripple.js"],"sourcesContent":["import{defineShader,ControlType}from\"framer\";export default defineShader({title:\"Ripple\",mouse:\"enabledByDefault\",buffers:[{name:\"rippleTrail\",resolutionScale:1,format:\"rg16f\",fragment:`\n                void main() {\n                    vec2 uv = v_uv;\n                    vec2 res = u_resolution.xy / u_pixelRatio;\n\n                    // Aspect/scale-independent reference length\n                    float ref = sqrt(res.x * res.y);\n\n                    // Fixed simulation grid with constant cell count, square cells in screen space\n                    const float simDetail = 800.;\n                    vec2 simRes = res / ref * simDetail;\n                    vec2 px = 1.0 / simRes;\n\n                    vec4 prevData = texture(u_rippleTrail_buffer, uv);\n                    float current = prevData.r;\n                    float previous = prevData.g;\n\n                    float left  = texture(u_rippleTrail_buffer, uv - vec2(px.x, 0.0)).r;\n                    float right = texture(u_rippleTrail_buffer, uv + vec2(px.x, 0.0)).r;\n                    float down  = texture(u_rippleTrail_buffer, uv - vec2(0.0, px.y)).r;\n                    float up    = texture(u_rippleTrail_buffer, uv + vec2(0.0, px.y)).r;\n\n                    float neighborAverage = (left + right + down + up) * 0.25;\n                    float r = u_deltaTime * 60.0;\n                    float decay = pow(mix(0.95, 0.99, u_trailDecay), r);\n                    float next = (neighborAverage * 2.0 - previous) * decay;\n\n                    // Distance measured in reference units\n                    vec2 p     = uv * res / ref;\n                    vec2 mouse = u_mousePosition.xy * res / ref;\n                    float dist = distance(p, mouse);\n\n                    // --- drag stamp (shows while pressing + moving) ---\n                    float stamp = 1.0 - smoothstep(u_trailRadius * (1.0 - u_trailSoftness), u_trailRadius, dist);\n                    float centerFade = smoothstep(0.0, u_centerFade * 0.2, dist);\n\n                    float speed = length(u_mousePosition.zw);\n                    float speedAmount = clamp(speed * u_trailStrength, 0.0, 1.0);\n                    float pressAmount = 1.0 + u_mousePointerDown * 1.5;\n\n                    next += stamp * centerFade * u_mouseHover * speedAmount * pressAmount * min(r, 3.0); //*r for fps normalization\n\n                    fragColor = vec4(next, current, 0.0, 1.0);\n                }\n            `},{// Multi-slot click state, split into SLOTS vertical bands.\n// Band 0  = press tracker:  r = previous eased u_mousePointerDown, g = was-rising flag,\n//                           b = seconds since last spawn.\n// Bands 1..SLOTS-1 = ripples:  rg = origin (uv),  b = age (-1 inactive).\n// u_mousePointerDown can be a Motion spring (eased 0..1), so a new press is detected\n// as the leading edge of its *rise* rather than a fixed threshold crossing.\nname:\"clickState\",resolutionScale:.5,format:\"rgba16f\",fragment:`\n                const int SLOTS = 9; // band 0 reserved + 8 ripples — keep in sync with main pass\n\n                vec4 readSlot(int i) {\n                    return texture(u_clickState_buffer, vec2((float(i) + 0.5) / float(SLOTS), 0.5));\n                }\n\n                // Ripple lifetime derived from the shared Decay prop.\n                float clickLifetime() {\n                    float f = mix(0.9, 0.95, u_trailDecay);\n                    return -3.0 / (log(f) / log(10.0)) / 60.0;\n                }\n\n                void main() {\n                    int s = int(clamp(floor(v_uv.x * float(SLOTS)), 0.0, float(SLOTS - 1)));\n\n                    float life = clickLifetime();\n\n                    // u_mousePointerDown is an EASED Motion spring value, not a hard 0/1.\n                    // Detect the leading edge of the rise instead.\n                    float down = u_mousePointerDown;\n\n                    // Band 0 holds last frame's state:\n                    //   .r = previous eased value   .g = was-rising flag   .b = seconds since last spawn\n                    vec4  g         = readSlot(0);\n                    float prevDown  = g.r;\n                    float wasRising = g.g;\n                    float cooldown  = g.b;\n                    if (g == vec4(0.0)) cooldown = 100.0; // fresh buffer: allow the first spawn\n\n                    float delta    = down - prevDown;\n                    bool  rising    = (delta > 0.003);                  // real upward impulse, tunable\n                    bool  spawnNow  = rising && (wasRising < 0.5)       // just started rising = a new press\n                                      && (cooldown > 0.05);             // debounce spring ringing / double-fire\n\n                    if (s == 0) {\n                        // Carry the eased value + trend forward, and reset the spawn cooldown.\n                        float nextCooldown = spawnNow ? 0.0 : min(cooldown + u_deltaTime, 100.0);\n                        fragColor = vec4(down, rising ? 1.0 : 0.0, nextCooldown, 0.0);\n                        return;\n                    }\n\n                    // --- ripple bands ---\n                    vec4 next = readSlot(s);\n                    if (next == vec4(0.0)) next = vec4(0.0, 0.0, -1.0, 0.0); // first-touch init\n\n                    // Age + expire.\n                    if (next.b >= 0.0) {\n                        float a = next.b + u_deltaTime;\n                        next.b = (a > life) ? -1.0 : a;\n                    }\n\n                    // Pick target ripple slot: free first, else oldest active.\n                    int   target = 1;\n                    float best   = -1e9;\n                    for (int i = 1; i < SLOTS; i++) {\n                        float age   = readSlot(i).b;\n                        float score = (age < 0.0) ? 1e6 : age;\n                        if (score > best) { best = score; target = i; }\n                    }\n\n                    // Spawn at the live cursor on the leading edge of a press.\n                    // u_mousePosition.xy (not g.rg) because band 0 only commits the new\n                    // origin this frame, while every band reads last frame's buffer.\n                    if (spawnNow && s == target) {\n                        next.rg = u_mousePosition.xy;\n                        next.b  = 0.0;\n                    }\n\n                    fragColor = next;\n                }\n            `}],fragment:`\n        const int SLOTS = 9; // keep in sync with clickState\n\n        vec2 coverFit(vec2 uv) {\n            vec2 flipped = vec2(uv.x, 1.0 - uv.y);\n            vec2 texRes = vec2(textureSize(u_texture, 0));\n            vec2 ratio = u_resolution.xy / texRes;\n            float maxRatio = max(ratio.x, ratio.y);\n            return (flipped - 0.5) * (ratio / maxRatio) + 0.5;\n        }\n\n        // Ripple lifetime derived from the shared Decay prop.\n        // Must match clickState's clickLifetime() exactly.\n        float clickLifetime() {\n            float f = mix(0.9, 0.95, u_trailDecay);\n            return -3.0 / (log(f) / log(10.0)) / 60.0;\n        }\n\n        // Analytic expanding ring for one ripple\n        vec2 clickRingOffset(vec2 uv, vec2 res, float ref, vec2 originUV, float age) {\n            vec2 p      = uv * res / ref;\n            vec2 origin = originUV * res / ref;\n            vec2 d      = p - origin;\n            float dist  = length(d);\n            if (dist < 1e-5) return vec2(0.0);\n            vec2 dir    = d / dist;\n\n            float life = clickLifetime();\n\n            // Normalized progress\n            float t = clamp(u_clickSpeed * age / life, 0.0, 1.0);\n\n            float reach = u_trailRadius * 3.0;\n            float front = reach * t;\n            float k    = 6.2831853 / max(0.25 * reach, 1e-4); // wavelength\n            float band = max(0.25 * reach, 1e-3);              // bandwidth  \n\n            float x        = dist - front;\n            float envelope = exp(-(x * x) / (band * band));\n            float fade     = 1.0 - smoothstep(life * 0.6, life, age);\n            float wave     = cos(k * x) * envelope * fade;\n\n            vec2 dirUV = normalize(dir * ref / res);\n            return dirUV * wave * u_trailStrength * 0.02;\n        }\n\n        void main() {\n            vec2 uv = v_uv;\n            vec2 res = u_resolution.xy / u_pixelRatio;\n            float ref = sqrt(res.x * res.y);\n\n            // Must match the buffer pass.\n            const float simDetail = 800.0;\n            vec2 simRes = res / ref * simDetail;\n            vec2 px = 1.0 / simRes;\n\n            float hL = texture(u_rippleTrail_buffer, uv - vec2(px.x, 0.0)).r;\n            float hR = texture(u_rippleTrail_buffer, uv + vec2(px.x, 0.0)).r;\n            float hD = texture(u_rippleTrail_buffer, uv - vec2(0.0, px.y)).r;\n            float hU = texture(u_rippleTrail_buffer, uv + vec2(0.0, px.y)).r;\n\n            // Drag trail: gradient of the simulated height field.\n            vec2 grad = vec2(hL - hR, hD - hU);\n            vec2 dragOffset = grad * u_trailDisplacement * ref / res;\n\n            // Click ripples: sum every active analytic ring (bands 1..SLOTS-1).\n            vec2 clickOffset = vec2(0.0);\n            if (u_clickRipple > 0.5) {\n                float life = clickLifetime();\n                for (int i = 1; i < SLOTS; i++) {\n                    vec4 slot = texture(u_clickState_buffer, vec2((float(i) + 0.5) / float(SLOTS), 0.5));\n                    float age = slot.b;\n                    if (age < 0.0 || age > life) continue;\n                    clickOffset += clickRingOffset(uv, res, ref, slot.rg, age);\n                }\n            }\n\n            vec2 baseOffset = dragOffset + clickOffset;\n\n            vec2 offsetR = baseOffset * (1.0 - u_dispersion * 0.5);\n            vec2 offsetG = baseOffset;\n            vec2 offsetB = baseOffset * (1.0 + u_dispersion * 0.5);\n\n            vec4 sR = texture(u_texture, coverFit(uv + offsetR));\n            vec4 sG = texture(u_texture, coverFit(uv + offsetG));\n            vec4 sB = texture(u_texture, coverFit(uv + offsetB));\n\n            fragColor = vec4(sR.r, sG.g, sB.b, sG.a); //or originally alpha 1.\n        }\n    `,propertyControls:{texture:{type:ControlType.ResponsiveImage,title:\"Image\",defaultValue:\"data:framer/asset-reference,YLF7DUmoyNPMPztlLCvoLWHHN88.png?originalFilename=Silhouette%402x.png&width=1024&height=1024\"},trailRadius:{type:ControlType.Number,title:\"Radius\",defaultValue:.1,min:.01,max:.4,step:.01},centerFade:{type:ControlType.Number,title:\"Fade\",defaultValue:.25,min:0,max:1,step:.01},trailSoftness:{type:ControlType.Number,title:\"Softness\",defaultValue:.5,min:0,max:1,step:.01,hidden:true},trailStrength:{type:ControlType.Number,title:\"Strength\",defaultValue:.5,min:.1,max:3,step:.01},trailDecay:{type:ControlType.Number,title:\"Decay\",defaultValue:.5,min:.01,max:1,step:.01},trailDisplacement:{type:ControlType.Number,title:\"Displace\",defaultValue:.03,min:.01,max:.15,step:.001},dispersion:{type:ControlType.Number,title:\"Dispersion\",defaultValue:0,min:0,max:1,step:.01},clickRipple:{type:ControlType.Boolean,title:\"Click\",defaultValue:true,section:\"Mouse\"},clickSpeed:{type:ControlType.Number,title:\"Speed\",defaultValue:1,min:.05,max:3,step:.01,section:\"Mouse\",hidden:true}}});\nexport const __FramerMetadata__ = {\"exports\":{\"default\":{\"type\":\"shader\",\"name\":null,\"annotations\":{\"framerContractVersion\":\"1\"}},\"__FramerMetadata__\":{\"type\":\"variable\"}}}\n//# sourceMappingURL=./Ripple.map"],"mappings":"8HAA4C,IAAgB,EAAa,CAAC,MAAM,SAAS,MAAM,mBAAmB,QAAQ,CAAC,CAAC,KAAK,cAAc,gBAAgB,EAAE,OAAO,QAAQ,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA4C5K,EAAE,CAMf,KAAK,aAAa,gBAAgB,GAAG,OAAO,UAAU,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAuElD,CAAC,EAAE,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAyFnB,iBAAiB,CAAC,QAAQ,CAAC,KAAK,EAAY,gBAAgB,MAAM,QAAQ,aAAa,yHAAyH,EAAE,YAAY,CAAC,KAAK,EAAY,OAAO,MAAM,SAAS,aAAa,GAAG,IAAI,IAAI,IAAI,GAAG,KAAK,GAAG,EAAE,WAAW,CAAC,KAAK,EAAY,OAAO,MAAM,OAAO,aAAa,IAAI,IAAI,EAAE,IAAI,EAAE,KAAK,GAAG,EAAE,cAAc,CAAC,KAAK,EAAY,OAAO,MAAM,WAAW,aAAa,GAAG,IAAI,EAAE,IAAI,EAAE,KAAK,IAAI,OAAO,EAAI,EAAE,cAAc,CAAC,KAAK,EAAY,OAAO,MAAM,WAAW,aAAa,GAAG,IAAI,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,WAAW,CAAC,KAAK,EAAY,OAAO,MAAM,QAAQ,aAAa,GAAG,IAAI,IAAI,IAAI,EAAE,KAAK,GAAG,EAAE,kBAAkB,CAAC,KAAK,EAAY,OAAO,MAAM,WAAW,aAAa,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,WAAW,CAAC,KAAK,EAAY,OAAO,MAAM,aAAa,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,GAAG,EAAE,YAAY,CAAC,KAAK,EAAY,QAAQ,MAAM,QAAQ,aAAa,GAAK,QAAQ,OAAO,EAAE,WAAW,CAAC,KAAK,EAAY,OAAO,MAAM,QAAQ,aAAa,EAAE,IAAI,IAAI,IAAI,EAAE,KAAK,IAAI,QAAQ,QAAQ,OAAO,EAAI,CAAC,CAAC,CAAC"}