[
  {
    "path": ".gitignore",
    "content": "gifs\n"
  },
  {
    "path": "README.md",
    "content": "# Cursor shaders for ghostty\n## WARNING: These are extremely customizable\n\n## Demos\n\n| Effect                              | Demo                                                                                                 |\n| --------                            | ------                                                                                               |\n| Cursor Warp<br>(Neovide-like)       | ![cursor_warp](https://github.com/user-attachments/assets/5323330c-e09d-4d80-963b-f0cb8413cac9)      |\n| Cursor Sweep                        | ![cursor_sweep](https://github.com/user-attachments/assets/c8979569-e0fa-48f1-afd7-9eed36df7f0a)     |\n| Cursor Tail<br>(Kitty-like)         | ![cursor_tail](https://github.com/user-attachments/assets/0c1ecd67-8ff4-4198-9e89-a4435289bfa0)      |\n| Ripple Cursor                       | ![ripple_cursor](https://github.com/user-attachments/assets/e489f74e-620a-490a-b5c5-d3918a5077c1)    |\n| Sonic Boom                          | ![sonic_boom](https://github.com/user-attachments/assets/91ac80e6-aa2b-41a3-8b49-d674ce287709)       |\n| Ripple Rectangle                    | ![ripple_rectangle](https://github.com/user-attachments/assets/5c8028eb-6ffb-4e38-a5dd-e2c0ed6a4175) |\n| Customized<br>(faded warp + ripple) | ![customized_warp](https://github.com/user-attachments/assets/3be0d82e-2bff-48ab-824e-3262cbb10d4d)  |\n\n## Trails\n- [cursor_warp.glsl](cursor_warp.glsl): Neovide-like cursor trail, most customizable shader\n- [cursor_sweep.glsl](cursor_sweep.glsl): Animated trail that shrinks from previous to current cursor position\n- [cursor_tail.glsl](cursor_tail.glsl): Comet-like trail, mimicing kitty terminal's cursor_trail effect\n\n## Pulse/Boom effects\n- These trigger on cursor mode changes (block to line or vice versa, looks cool on changing modes in vim)\n- [sonic_boom_cursor.glsl](sonic_boom_cursor.glsl): expanding filled circle \n- [ripple_cursor.glsl](ripple_cursor.glsl): Expanding circular ring ripple effect\n- [rectangle_boom_cursor.glsl](rectangle_boom_cursor.glsl): Same as boom_cursor but rectangular(cursor shape)\n- [ripple_rectangle_cursor.glsl](ripple_rectangle_cursor.glsl): Same as ripple_cursor but rectangular(cursor shape)\n\n\n> [!NOTE]\n> If you have the line cursor (default), these effects will trigger and freeze on unfocus(as cursor changes to hollow block). The solution is to add `custom-shader-animation = always` to your ghostty config\n\n## Usage\n\n1. Clone the repo into your ghostty shaders directory:\n```bash\ngit clone https://github.com/sahaj-b/ghostty-cursor-shaders ~/.config/ghostty/shaders\n```\n\n2. In your `~/.config/ghostty/config`, add:\n```config\ncustom-shader = shaders/yourshader1.glsl\ncustom-shader = shaders/yourshader2.glsl\n# ...\n```\nReplace `yourshader` with the name of any shader file (e.g., `cursor_sweep`, `ripple_cursor`, etc.)\n\n\n## Customization\n- All shaders has customizable parameters (like color, duration, size, thickness, etc) etc at the top of each file. You can adjust\n- Also, all files has various **Easing Functions** to choose from.\n  - these function control the animation curve of the effects, you can make them elasitcy, springy, smooth, linear, etc by changing the easing function\n  - in trail shaders, you can comment/uncomment the easing functions in the code\n  - in pulse/boom shaders, you can comment/uncomment the lines in the `ANIMATION` section\n  - you can also add your own easing functions if you want\n\n### Example (faded warp + ripple)\n```glsl\n// in cursor_warp.glsl\nconst float DURATION = 0.15;\nconst float TRAIL_SIZE = 0.8;\nconst float THRESHOLD_MIN_DISTANCE = 1.0;\nconst float BLUR = 1.0;\nconst float TRAIL_THICKNESS = 1.0;\nconst float TRAIL_THICKNESS_X = 0.9;\n\nconst float FADE_ENABLED = 1.0;\nconst float FADE_EXPONENT = 5.0;\n```\n```glsl\n// in ripple_cursor.glsl\nconst float DURATION = 0.15;\nconst float MAX_RADIUS = 0.026;\nconst float RING_THICKNESS = 0.02;\nconst float CURSOR_WIDTH_CHANGE_THRESHOLD = 0.5;\nvec4 COLOR = vec4(0.35, 0.36, 0.44, 0.8);\nconst float BLUR = 3.5;\nconst float ANIMATION_START_OFFSET = 0.01;\n```\n\n## Acknowledgements\nInspired by [Neovide](https://neovide.dev/) cursor animations and [KroneCorylus/ghostty-shader-playground](https://github.com/KroneCorylus/ghostty-shader-playground)\n\n## License\nMIT\n\n## Why use branching(if/else) instead of branchless math\n- coz we are dealing with **uniform branching** here, which has **NO DIVERGENCE**.\n- ie, all fragments will take the same branch path, so no performance penalty on modern GPUs\n- Branchless math would force GPU to calculate animations every single frame, even when there is no need\n"
  },
  {
    "path": "cursor_sweep.glsl",
    "content": "// -- CONFIGURATION ---\nvec4 TRAIL_COLOR = iCurrentCursorColor; // can change to eg: vec4(0.2, 0.6, 1.0, 0.5);\nconst float DURATION = 0.2; // in seconds\nconst float TRAIL_LENGTH = 0.5;\nconst float BLUR = 2.0; // blur size in pixels (for antialiasing)\n\n// --- CONSTANTS for easing functions ---\nconst float PI = 3.14159265359;\nconst float C1_BACK = 1.70158;\nconst float C2_BACK = C1_BACK * 1.525;\nconst float C3_BACK = C1_BACK + 1.0;\nconst float C4_ELASTIC = (2.0 * PI) / 3.0;\nconst float C5_ELASTIC = (2.0 * PI) / 4.5;\nconst float SPRING_STIFFNESS = 9.0;\nconst float SPRING_DAMPING = 0.9;\n\n// --- EASING FUNCTIONS ---\n\n// // Linear\n// float ease(float x) {\n//     return x;\n// }\n\n// // EaseOutQuad\n// float ease(float x) {\n//     return 1.0 - (1.0 - x) * (1.0 - x);\n// }\n\n// EaseOutCubic\nfloat ease(float x) {\n    return 1.0 - pow(1.0 - x, 3.0);\n}\n\n// // EaseOutQuart\n// float ease(float x) {\n//     return 1.0 - pow(1.0 - x, 4.0);\n// }\n\n// // EaseOutQuint\n// float ease(float x) {\n//     return 1.0 - pow(1.0 - x, 5.0);\n// }\n\n// EaseOutSine\n// float ease(float x) {\n//     return sin((x * PI) / 2.0);\n// }\n\n// // EaseOutExpo\n// float ease(float x) {\n//     return x == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * x);\n// }\n\n// // EaseOutCirc\n// float ease(float x) {\n//     return sqrt(1.0 - pow(x - 1.0, 2.0));\n// }\n\n// // EaseOutBack\n// float ease(float x) {\n//     return 1.0 + C3_BACK * pow(x - 1.0, 3.0) + C1_BACK * pow(x - 1.0, 2.0);\n// }\n\n// // EaseOutElastic\n// float ease(float x) {\n//     return x == 0.0 ? 0.0\n//          : x == 1.0 ? 1.0\n//                     : pow(2.0, -10.0 * x) * sin((x * 10.0 - 0.75) * C4_ELASTIC) + 1.0;\n// }\n\n// Parametric Spring\n// float ease(float x) {\n//     x = clamp(x, 0.0, 1.0);\n//     float decay = exp(-SPRING_DAMPING * SPRING_STIFFNESS * x);\n//     float freq = sqrt(SPRING_STIFFNESS * (1.0 - SPRING_DAMPING * SPRING_DAMPING));\n//     float osc = cos(freq * 6.283185 * x) + (SPRING_DAMPING * sqrt(SPRING_STIFFNESS) / freq) * sin(freq * 6.283185 * x);\n//     return 1.0 - decay * osc;\n// }\n\nfloat getSdfRectangle(in vec2 point, in vec2 center, in vec2 halfSize)\n{\n    vec2 d = abs(point - center) - halfSize;\n    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);\n}\n\n// Based on Inigo Quilez's 2D distance functions article: https://iquilezles.org/articles/distfunctions2d/\n// Potencially optimized by eliminating conditionals and loops to enhance performance and reduce branching\n\nfloat seg(in vec2 p, in vec2 a, in vec2 b, inout float s, float d) {\n    vec2 e = b - a;\n    vec2 w = p - a;\n    vec2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);\n    float segd = dot(p - proj, p - proj);\n    d = min(d, segd);\n\n    float c0 = step(0.0, p.y - a.y);\n    float c1 = 1.0 - step(0.0, p.y - b.y);\n    float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);\n    float allCond = c0 * c1 * c2;\n    float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);\n    float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));\n    s *= flip;\n    return d;\n}\n\nfloat getSdfParallelogram(in vec2 p, in vec2 v0, in vec2 v1, in vec2 v2, in vec2 v3) {\n    float s = 1.0;\n    float d = dot(p - v0, p - v0);\n\n    d = seg(p, v0, v3, s, d);\n    d = seg(p, v1, v0, s, d);\n    d = seg(p, v2, v1, s, d);\n    d = seg(p, v3, v2, s, d);\n\n    return s * sqrt(d);\n}\n\nvec2 normalize(vec2 value, float isPosition) {\n    return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;\n}\n\nfloat antialising(float distance) {\n\treturn 1. - smoothstep(0., normalize(vec2(BLUR, BLUR), 0.).x, distance);\n}\n\nfloat getTopVertexFlag(vec2 a, vec2 b) {\n    float condition1 = step(b.x, a.x) * step(a.y, b.y); // a.x < b.x && a.y > b.y\n    float condition2 = step(a.x, b.x) * step(b.y, a.y); // a.x > b.x && a.y < b.y\n\n    // if neither condition is met, return 1 (else case)\n    return 1.0 - max(condition1, condition2);\n}\n\nvec2 getRectangleCenter(vec4 rectangle) {\n    return vec2(rectangle.x + (rectangle.z / 2.), rectangle.y - (rectangle.w / 2.));\n}\n\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord){\n    #if !defined(WEB)\n    fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);\n    #endif\n\n    // normalization & setup(-1, 1 coords)\n    vec2 vu = normalize(fragCoord, 1.);\n    vec2 offsetFactor = vec2(-.5, 0.5);\n    \n    vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));\n    vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));\n\n    vec2 centerCC = currentCursor.xy - (currentCursor.zw * offsetFactor);\n    vec2 centerCP = previousCursor.xy - (previousCursor.zw * offsetFactor);\n\n    float sdfCurrentCursor = getSdfRectangle(vu, centerCC, currentCursor.zw * 0.5);\n    \n     float lineLength = distance(centerCC, centerCP);\n\t\n     vec4 newColor = vec4(fragColor);\n\t\n     float minDist = currentCursor.w * 1.5;\n     float progress = clamp((iTime - iTimeCursorChange) / DURATION, 0.0, 1.0);\n     if (lineLength > minDist) {\n         // --- Animation Logic ---\n         float shrinkFactor = ease(progress);\n\n        // detect straight moves\n        vec2 delta = abs(centerCC - centerCP);\n        float threshold = 0.001;\n        float isHorizontal = step(delta.y, threshold);\n        float isVertical = step(delta.x, threshold);\n        float isStraightMove = max(isHorizontal, isVertical);\n\n        // -- Making parallelogram sdf (diagonal moves) ---\n        float topVertexFlag = getTopVertexFlag(currentCursor.xy, previousCursor.xy);\n        float bottomVertexFlag = 1.0 - topVertexFlag;\n        vec2 v0 = vec2(currentCursor.x + currentCursor.z * topVertexFlag, currentCursor.y - currentCursor.w);\n        vec2 v1 = vec2(currentCursor.x + currentCursor.z * bottomVertexFlag, currentCursor.y);\n        vec2 v2_full = vec2(previousCursor.x + currentCursor.z * bottomVertexFlag, previousCursor.y);\n        vec2 v3_full = vec2(previousCursor.x + currentCursor.z * topVertexFlag, previousCursor.y - previousCursor.w);\n\n        vec2 v2_start = mix(v1, v2_full, TRAIL_LENGTH);\n        vec2 v3_start = mix(v0, v3_full, TRAIL_LENGTH);\n        vec2 v2_anim = mix(v2_start, v1, shrinkFactor);\n        vec2 v3_anim = mix(v3_start, v0, shrinkFactor);\n        \n        float sdfTrail_diag = getSdfParallelogram(vu, v0, v1, v2_anim, v3_anim);\n\n        // --- Making rectangle sdf (straight moves) ---\n        vec2 min_center = min(centerCP, centerCC);\n        vec2 max_center = max(centerCP, centerCC);\n\n        vec2 bBoxSize_full = (max_center - min_center) + currentCursor.zw;\n        vec2 bBoxCenter_full = (min_center + max_center) * 0.5;\n\n        vec2 bBoxSize_start = mix(currentCursor.zw, bBoxSize_full, TRAIL_LENGTH);\n        vec2 bBoxCenter_start = mix(centerCC, bBoxCenter_full, TRAIL_LENGTH);\n\n        vec2 animSize = mix(bBoxSize_start, currentCursor.zw, shrinkFactor);\n        vec2 animCenter = mix(bBoxCenter_start, centerCC, shrinkFactor);\n\n        float sdfTrail_rect = getSdfRectangle(vu, animCenter, animSize * 0.5);\n\n        // -- Selecting and drawing the trail sdf --\n        float sdfTrail = mix(sdfTrail_diag, sdfTrail_rect, isStraightMove);\n\n        vec4 trail = TRAIL_COLOR;\n        float trailAlpha = antialising(sdfTrail);\n        newColor = mix(newColor, trail, trailAlpha);\n\n        // Punch hole\n        newColor = mix(newColor, fragColor, step(sdfCurrentCursor, 0.));\n    }\n\n\n    fragColor = newColor;\n}\n"
  },
  {
    "path": "cursor_tail.glsl",
    "content": "// -- CONFIGURATION --\nvec4 TRAIL_COLOR = iCurrentCursorColor; // can change to eg: vec4(0.2, 0.6, 1.0, 0.5);\nconst float DURATION = 0.09; // in seconds\nconst float MAX_TRAIL_LENGTH = 0.2;\nconst float THRESHOLD_MIN_DISTANCE = 1.5; // min distance to show trail (units of cursor width)\nconst float BLUR = 2.0; // blur size in pixels (for antialiasing)\n\n// --- CONSTANTS for easing functions ---\nconst float PI = 3.14159265359;\nconst float C1_BACK = 1.70158;\nconst float C2_BACK = C1_BACK * 1.525;\nconst float C3_BACK = C1_BACK + 1.0;\nconst float C4_ELASTIC = (2.0 * PI) / 3.0;\nconst float C5_ELASTIC = (2.0 * PI) / 4.5;\nconst float SPRING_STIFFNESS = 9.0;\nconst float SPRING_DAMPING = 0.9;\n\n// --- EASING FUNCTIONS ---\n\n// // Linear\n// float ease(float x) {\n//     return x;\n// }\n\n// // EaseOutQuad\n// float ease(float x) {\n//     return 1.0 - (1.0 - x) * (1.0 - x);\n// }\n\n// // EaseOutCubic\n// float ease(float x) {\n//     return 1.0 - pow(1.0 - x, 3.0);\n// }\n\n\n// // EaseOutQuart\n// float ease(float x) {\n//     return 1.0 - pow(1.0 - x, 4.0);\n// }\n\n// // EaseOutQuint\n// float ease(float x) {\n//     return 1.0 - pow(1.0 - x, 5.0);\n// }\n\n// // EaseOutSine\n// float ease(float x) {\n//     return sin((x * PI) / 2.0);\n// }\n\n// // EaseOutExpo\n// float ease(float x) {\n//     return x == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * x);\n// }\n\n// EaseOutCirc\nfloat ease(float x) {\n    return sqrt(1.0 - pow(x - 1.0, 2.0));\n}\n\n// // EaseOutBack\n// float ease(float x) {\n//     return 1.0 + C3_BACK * pow(x - 1.0, 3.0) + C1_BACK * pow(x - 1.0, 2.0);\n// }\n\n// // EaseOutElastic\n// float ease(float x) {\n//     return x == 0.0 ? 0.0\n//          : x == 1.0 ? 1.0\n//                     : pow(2.0, -10.0 * x) * sin((x * 10.0 - 0.75) * C4_ELASTIC) + 1.0;\n// }\n\n// Parametric Spring\n// float ease(float x) {\n//     x = clamp(x, 0.0, 1.0);\n//     float decay = exp(-SPRING_DAMPING * SPRING_STIFFNESS * x);\n//     float freq = sqrt(SPRING_STIFFNESS * (1.0 - SPRING_DAMPING * SPRING_DAMPING));\n//     float osc = cos(freq * 6.283185 * x) + (SPRING_DAMPING * sqrt(SPRING_STIFFNESS) / freq) * sin(freq * 6.283185 * x);\n//     return 1.0 - decay * osc;\n// }\n\nfloat getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b)\n{\n    vec2 d = abs(p - xy) - b;\n    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);\n}\n\n// Based on Inigo Quilez's 2D distance functions article: https://iquilezles.org/articles/distfunctions2d/\n// Potencially optimized by eliminating conditionals and loops to enhance performance and reduce branching\n\nfloat seg(in vec2 p, in vec2 a, in vec2 b, inout float s, float d) {\n    vec2 e = b - a;\n    vec2 w = p - a;\n    vec2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);\n    float segd = dot(p - proj, p - proj);\n    d = min(d, segd);\n\n    float c0 = step(0.0, p.y - a.y);\n    float c1 = 1.0 - step(0.0, p.y - b.y);\n    float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);\n    float allCond = c0 * c1 * c2;\n    float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);\n    float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));\n    s *= flip;\n    return d;\n}\n\nfloat getSdfParallelogram(in vec2 p, in vec2 v0, in vec2 v1, in vec2 v2, in vec2 v3) {\n    float s = 1.0;\n    float d = dot(p - v0, p - v0);\n\n    d = seg(p, v0, v3, s, d);\n    d = seg(p, v1, v0, s, d);\n    d = seg(p, v2, v1, s, d);\n    d = seg(p, v3, v2, s, d);\n\n    return s * sqrt(d);\n}\n\nvec2 normalize(vec2 value, float isPosition) {\n    return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;\n}\n\nfloat antialising(float distance) {\n\treturn 1. - smoothstep(0., normalize(vec2(BLUR, BLUR), 0.).x, distance);\n}\n\nfloat determineIfTopRightIsLeading(vec2 a, vec2 b) {\n    float condition1 = step(b.x, a.x) * step(a.y, b.y); // a.x < b.x && a.y > b.y\n    float condition2 = step(a.x, b.x) * step(b.y, a.y); // a.x > b.x && a.y < b.y\n\n    // if neither condition is met, return 1 (else case)\n    return 1.0 - max(condition1, condition2);\n}\n\nvec2 getRectangleCenter(vec4 rectangle) {\n    return vec2(rectangle.x + (rectangle.z / 2.), rectangle.y - (rectangle.w / 2.));\n}\n\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord){\n    #if !defined(WEB)\n    fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);\n    #endif\n\n    // normalization & setup(-1, 1 coords)\n    vec2 vu = normalize(fragCoord, 1.);\n    vec2 offsetFactor = vec2(-.5, 0.5);\n    \n    vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));\n    vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));\n\n    vec2 centerCC = currentCursor.xy - (currentCursor.zw * offsetFactor);\n    vec2 centerCP = previousCursor.xy - (previousCursor.zw * offsetFactor);\n\n    vec2 delta = centerCP - centerCC;\n    float lineLength = length(delta);\n\n     float sdfCurrentCursor = getSdfRectangle(vu, centerCC, currentCursor.zw * 0.5);\n\t\n     vec4 newColor = vec4(fragColor);\n\t\n     float minDist = currentCursor.w * THRESHOLD_MIN_DISTANCE;\n     float progress = clamp((iTime - iTimeCursorChange) / DURATION, 0.0, 1.0);\n     if (lineLength > minDist) {\n         // ANIMATION logic\n        \n        float head_eased = 0.0;\n        float tail_eased = 0.0;\n\n        float tail_delay_factor = MAX_TRAIL_LENGTH / lineLength;\n\n        float isLongMove = step(MAX_TRAIL_LENGTH, lineLength);\n\n        float head_eased_short = ease(progress);\n        float tail_eased_short = ease(smoothstep(tail_delay_factor, 1.0, progress));\n        float head_eased_long = 1.0;\n        float tail_eased_long = ease(progress);\n\n        head_eased = mix(head_eased_long, head_eased_short, isLongMove);\n        tail_eased = mix(tail_eased_long, tail_eased_short, isLongMove);\n\n        // detect straight moves\n        vec2 delta_abs = abs(centerCC - centerCP); \n        float threshold = 0.001;\n        float isHorizontal = step(delta_abs.y, threshold);\n        float isVertical = step(delta_abs.x, threshold);\n        float isStraightMove = max(isHorizontal, isVertical);\n\n        // -- Making the parallelogram sdf (diagonal move) --\n\n        // animate the TOP-LEFT corners\n        vec2 head_pos_tl = mix(previousCursor.xy, currentCursor.xy, head_eased);\n        vec2 tail_pos_tl = mix(previousCursor.xy, currentCursor.xy, tail_eased);\n\n        float isTopRightLeading = determineIfTopRightIsLeading(currentCursor.xy, previousCursor.xy);\n        float isBottomLeftLeading = 1.0 - isTopRightLeading;\n        \n        // v0, v1 : \"front\" of the trail (head)\n        vec2 v0 = vec2(head_pos_tl.x + currentCursor.z * isTopRightLeading, head_pos_tl.y - currentCursor.w);\n        vec2 v1 = vec2(head_pos_tl.x + currentCursor.z * isBottomLeftLeading, head_pos_tl.y);\n        \n        // v2, v3: \"back\" of the trail (tail)\n        vec2 v2 = vec2(tail_pos_tl.x + currentCursor.z * isBottomLeftLeading, tail_pos_tl.y);\n        vec2 v3 = vec2(tail_pos_tl.x + currentCursor.z * isTopRightLeading, tail_pos_tl.y - previousCursor.w);\n\n        float sdfTrail_diag = getSdfParallelogram(vu, v0, v1, v2, v3);\n\n        // -- Making the rectangle sdf (straight move) --\n\n        vec2 head_center = mix(centerCP, centerCC, head_eased);\n        vec2 tail_center = mix(centerCP, centerCC, tail_eased);\n\n        vec2 min_center = min(head_center, tail_center);\n        vec2 max_center = max(head_center, tail_center);\n        \n        vec2 box_size = (max_center - min_center) + currentCursor.zw;\n        vec2 box_center = (min_center + max_center) * 0.5;\n\n        float sdfTrail_rect = getSdfRectangle(vu, box_center, box_size * 0.5);\n\n        // -- FINAL SELECTING AND DRAWING --\n        float sdfTrail = mix(sdfTrail_diag, sdfTrail_rect, isStraightMove);\n        \n        vec4 trail = TRAIL_COLOR;\n        float trailAlpha = antialising(sdfTrail);\n        newColor = mix(newColor, trail, trailAlpha);\n\n        // punch hole\n        newColor = mix(newColor, fragColor, step(sdfCurrentCursor, 0.));\n    }\n\n    fragColor = newColor;\n}\n"
  },
  {
    "path": "cursor_warp.glsl",
    "content": "// --- CONFIGURATION ---\nvec4 TRAIL_COLOR = iCurrentCursorColor; // can change to eg: vec4(0.2, 0.6, 1.0, 0.5);\nconst float DURATION = 0.2; // total animation time\nconst float TRAIL_SIZE = 0.8; // 0.0 = all corners move together. 1.0 = max smear (leading corners jump instantly)\nconst float THRESHOLD_MIN_DISTANCE = 1.5; // min distance to show trail (units of cursor height)\nconst float BLUR = 1.0; // blur size in pixels (for antialiasing)\nconst float TRAIL_THICKNESS = 1.0;  // 1.0 = full cursor height, 0.0 = zero height, >1.0 = funky aah\nconst float TRAIL_THICKNESS_X = 0.9;\n\nconst float FADE_ENABLED = 0.0; // 1.0 to enable fade gradient along the trail, 0.0 to disable\nconst float FADE_EXPONENT = 5.0; // exponent for fade gradient along the trail\n\n// --- CONSTANTS for easing functions ---\nconst float PI = 3.14159265359;\nconst float C1_BACK = 1.70158;\nconst float C2_BACK = C1_BACK * 1.525;\nconst float C3_BACK = C1_BACK + 1.0;\nconst float C4_ELASTIC = (2.0 * PI) / 3.0;\nconst float C5_ELASTIC = (2.0 * PI) / 4.5;\nconst float SPRING_STIFFNESS = 9.0;\nconst float SPRING_DAMPING = 0.9;\n\n// --- EASING FUNCTIONS ---\n\n// // Linear\n// float ease(float x) {\n//     return x;\n// }\n\n// // EaseOutQuad\n// float ease(float x) {\n//     return 1.0 - (1.0 - x) * (1.0 - x);\n// }\n\n// // EaseOutCubic\n// float ease(float x) {\n//     return 1.0 - pow(1.0 - x, 3.0);\n// }\n\n// // EaseOutQuart\n// float ease(float x) {\n//     return 1.0 - pow(1.0 - x, 4.0);\n// }\n\n// // EaseOutQuint\n// float ease(float x) {\n//     return 1.0 - pow(1.0 - x, 5.0);\n// }\n\n// // EaseOutSine\n// float ease(float x) {\n//     return sin((x * PI) / 2.0);\n// }\n\n// // EaseOutExpo\n// float ease(float x) {\n//     return x == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * x);\n// }\n\n// EaseOutCirc\nfloat ease(float x) {\n    return sqrt(1.0 - pow(x - 1.0, 2.0));\n}\n\n// // EaseOutBack\n// float ease(float x) {\n//     return 1.0 + C3_BACK * pow(x - 1.0, 3.0) + C1_BACK * pow(x - 1.0, 2.0);\n// }\n\n// // EaseOutElastic\n// float ease(float x) {\n//     return x == 0.0 ? 0.0\n//          : x == 1.0 ? 1.0\n//                     : pow(2.0, -10.0 * x) * sin((x * 10.0 - 0.75) * C4_ELASTIC) + 1.0;\n// }\n\n// // Parametric Spring\n// float ease(float x) {\n//     x = clamp(x, 0.0, 1.0);\n//     float decay = exp(-SPRING_DAMPING * SPRING_STIFFNESS * x);\n//     float freq = sqrt(SPRING_STIFFNESS * (1.0 - SPRING_DAMPING * SPRING_DAMPING));\n//     float osc = cos(freq * 6.283185 * x) + (SPRING_DAMPING * sqrt(SPRING_STIFFNESS) / freq) * sin(freq * 6.283185 * x);\n//     return 1.0 - decay * osc;\n// }\n\nfloat getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b)\n{\n    vec2 d = abs(p - xy) - b;\n    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);\n}\n\n// Based on Inigo Quilez's 2D distance functions article: https://iquilezles.org/articles/distfunctions2d/\n// Potencially optimized by eliminating conditionals and loops to enhance performance and reduce branching\nfloat seg(in vec2 p, in vec2 a, in vec2 b, inout float s, float d) {\n    vec2 e = b - a;\n    vec2 w = p - a;\n    vec2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);\n    float segd = dot(p - proj, p - proj);\n    d = min(d, segd);\n\n    float c0 = step(0.0, p.y - a.y);\n    float c1 = 1.0 - step(0.0, p.y - b.y);\n    float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);\n    float allCond = c0 * c1 * c2;\n    float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);\n    float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));\n    s *= flip;\n    return d;\n}\n\nfloat getSdfConvexQuad(in vec2 p, in vec2 v1, in vec2 v2, in vec2 v3, in vec2 v4) {\n    float s = 1.0;\n    float d = dot(p - v1, p - v1);\n\n    d = seg(p, v1, v2, s, d);\n    d = seg(p, v2, v3, s, d);\n    d = seg(p, v3, v4, s, d);\n    d = seg(p, v4, v1, s, d);\n\n    return s * sqrt(d);\n}\n\nvec2 normalize(vec2 value, float isPosition) {\n    return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;\n}\n\nfloat antialising(float distance, float blurAmount) {\n  return 1. - smoothstep(0., normalize(vec2(blurAmount, blurAmount), 0.).x, distance);\n}\n\n// Determines animation duration based on a corner's alignment with the move direction(dot product)\n// dot_val will be in [-2, 2]\n// > 0.5 (1 or 2) = Leading\n// > -0.5 (0)     = Side\n// <= -0.5 (-1 or -2) = Trailing\nfloat getDurationFromDot(float dot_val, float DURATION_LEAD, float DURATION_SIDE, float DURATION_TRAIL) {\n    float isLead = step(0.5, dot_val);\n    float isSide = step(-0.5, dot_val) * (1.0 - isLead);\n    \n    // Start with trailing duration\n    float duration = mix(DURATION_TRAIL, DURATION_SIDE, isSide);\n    // Mix in leading duration\n    duration = mix(duration, DURATION_LEAD, isLead);\n    return duration;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord){\n    #if !defined(WEB)\n    fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);\n    #endif\n\n    // normalization & setup(-1, 1 coords)\n    vec2 vu = normalize(fragCoord, 1.);\n    vec2 offsetFactor = vec2(-.5, 0.5);\n\n    vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));\n    vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));\n\n    vec2 centerCC = currentCursor.xy - (currentCursor.zw * offsetFactor);\n    vec2 halfSizeCC = currentCursor.zw * 0.5;\n    vec2 centerCP = previousCursor.xy - (previousCursor.zw * offsetFactor);\n    vec2 halfSizeCP = previousCursor.zw * 0.5;\n\n    float sdfCurrentCursor = getSdfRectangle(vu, centerCC, halfSizeCC);\n    \n    float lineLength = distance(centerCC, centerCP);\n    float minDist = currentCursor.w * THRESHOLD_MIN_DISTANCE;\n    \n    vec4 newColor = vec4(fragColor);\n\n    float baseProgress = iTime - iTimeCursorChange;\n    \n    if (lineLength > minDist && baseProgress < DURATION - 0.001) {\n        // defining corners of cursors\n\n        // Y (Height) with TRAIL_THICKNESS\n        float cc_half_height = currentCursor.w * 0.5;\n        float cc_center_y = currentCursor.y - cc_half_height;\n        float cc_new_half_height = cc_half_height * TRAIL_THICKNESS;\n        float cc_new_top_y = cc_center_y + cc_new_half_height;\n        float cc_new_bottom_y = cc_center_y - cc_new_half_height;\n\n        // X (Width) with TRAIL_THICKNESS\n        float cc_half_width = currentCursor.z * 0.5;\n        float cc_center_x = currentCursor.x + cc_half_width;\n        float cc_new_half_width = cc_half_width * TRAIL_THICKNESS_X;\n        float cc_new_left_x = cc_center_x - cc_new_half_width;\n        float cc_new_right_x = cc_center_x + cc_new_half_width;\n\n        vec2 cc_tl = vec2(cc_new_left_x, cc_new_top_y);\n        vec2 cc_tr = vec2(cc_new_right_x, cc_new_top_y);\n        vec2 cc_bl = vec2(cc_new_left_x, cc_new_bottom_y);\n        vec2 cc_br = vec2(cc_new_right_x, cc_new_bottom_y);\n\n        // same thing for previous cursor\n        float cp_half_height = previousCursor.w * 0.5;\n        float cp_center_y = previousCursor.y - cp_half_height;\n        float cp_new_half_height = cp_half_height * TRAIL_THICKNESS;\n        float cp_new_top_y = cp_center_y + cp_new_half_height;\n        float cp_new_bottom_y = cp_center_y - cp_new_half_height;\n\n        float cp_half_width = previousCursor.z * 0.5;\n        float cp_center_x = previousCursor.x + cp_half_width;\n        float cp_new_half_width = cp_half_width * TRAIL_THICKNESS_X;\n        float cp_new_left_x = cp_center_x - cp_new_half_width;\n        float cp_new_right_x = cp_center_x + cp_new_half_width;\n\n        vec2 cp_tl = vec2(cp_new_left_x, cp_new_top_y);\n        vec2 cp_tr = vec2(cp_new_right_x, cp_new_top_y);\n        vec2 cp_bl = vec2(cp_new_left_x, cp_new_bottom_y);\n        vec2 cp_br = vec2(cp_new_right_x, cp_new_bottom_y);\n\n        // calculating durations for every corner\n        const float DURATION_TRAIL = DURATION;\n        const float DURATION_LEAD = DURATION * (1.0 - TRAIL_SIZE);\n        const float DURATION_SIDE = (DURATION_LEAD + DURATION_TRAIL) / 2.0;\n\n        vec2 moveVec = centerCC - centerCP;\n        vec2 s = sign(moveVec);\n\n        // dot products for each corner, determining alignment with movement direction\n        float dot_tl = dot(vec2(-1., 1.), s);\n        float dot_tr = dot(vec2( 1., 1.), s);\n        float dot_bl = dot(vec2(-1.,-1.), s);\n        float dot_br = dot(vec2( 1.,-1.), s);\n\n        // assign durations based on dot products\n        float dur_tl = getDurationFromDot(dot_tl, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);\n        float dur_tr = getDurationFromDot(dot_tr, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);\n        float dur_bl = getDurationFromDot(dot_bl, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);\n        float dur_br = getDurationFromDot(dot_br, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);\n\n        // check direction of horizontal movement\n        float isMovingRight = step(0.5, s.x);\n        float isMovingLeft  = step(0.5, -s.x);\n\n        // calculate vertical-rail durations\n        float dot_right_edge = (dot_tr + dot_br) * 0.5;\n        float dur_right_rail = getDurationFromDot(dot_right_edge, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);\n        \n        float dot_left_edge = (dot_tl + dot_bl) * 0.5;\n        float dur_left_rail = getDurationFromDot(dot_left_edge, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);\n\n        float final_dur_tl = mix(dur_tl, dur_left_rail, isMovingLeft);\n        float final_dur_bl = mix(dur_bl, dur_left_rail, isMovingLeft);\n        \n        float final_dur_tr = mix(dur_tr, dur_right_rail, isMovingRight);\n        float final_dur_br = mix(dur_br, dur_right_rail, isMovingRight);\n\n        // calculate progress for each corner based on the duration and time since cursor change\n        float prog_tl = ease(clamp(baseProgress / final_dur_tl, 0.0, 1.0));\n        float prog_tr = ease(clamp(baseProgress / final_dur_tr, 0.0, 1.0));\n        float prog_bl = ease(clamp(baseProgress / final_dur_bl, 0.0, 1.0));\n        float prog_br = ease(clamp(baseProgress / final_dur_br, 0.0, 1.0));\n\n        // get the trial corner positions based on progress\n        vec2 v_tl = mix(cp_tl, cc_tl, prog_tl);\n        vec2 v_tr = mix(cp_tr, cc_tr, prog_tr);\n        vec2 v_br = mix(cp_br, cc_br, prog_br);\n        vec2 v_bl = mix(cp_bl, cc_bl, prog_bl);\n\n        // DRAWING THE TRAIL\n        float sdfTrail = getSdfConvexQuad(vu, v_tl, v_tr, v_br, v_bl);\n\n        // --- FADE GRADIENT CALCULATION ---\n        vec2 fragVec = vu - centerCP;\n        \n        // project fragment onto movement vector, normalize to [0, 1]\n        // 0.0 at tail, 1.0 at head\n        // tiny epsilon to avoid division by zero if moveVec is (0,0)\n        float fadeProgress = clamp(dot(fragVec, moveVec) / (dot(moveVec, moveVec) + 1e-6), 0.0, 1.0);\n\n        vec4 trail = TRAIL_COLOR;\n        \n        float effectiveBlur = BLUR;\n        if (BLUR < 2.5) {\n          // no antialising on horizontal/vertical movement, fixes 'pulse' like thing on end cursor\n          float isDiagonal = abs(s.x) * abs(s.y); // 1.0 if diagonal, 0.0 if H/V\n          float effectiveBlur = mix(0.0, BLUR, isDiagonal);\n        }\n        float shapeAlpha = antialising(sdfTrail, effectiveBlur); // shape mask\n\n        if (FADE_ENABLED > 0.5) {\n            // apply fade gradient along the trail\n            // float fadeStart = 0.2;\n            // float easedProgress = smoothstep(fadeStart, 1.0, fadeProgress);\n            // easedProgress = pow(2.0, 10.0 * (fadeProgress - 1.0));\n            float easedProgress = pow(fadeProgress, FADE_EXPONENT);\n            trail.a *= easedProgress;\n        }\n\n        float finalAlpha = trail.a * shapeAlpha;\n\n        // newColor.a to preserve the background alpha.\n        newColor = mix(newColor, vec4(trail.rgb, newColor.a), finalAlpha);\n\n        // punch hole on the trail, so current cursor is drawn on top\n        newColor = mix(newColor, fragColor, step(sdfCurrentCursor, 0.));\n\n    }\n\n    fragColor = newColor;\n}\n"
  },
  {
    "path": "rectangle_boom_cursor.glsl",
    "content": "// CONFIGURATION\nconst float DURATION = 0.15;               // How long the ripple animates (seconds)\nconst float MAX_SIZE = 0.05;             // Max radius in normalized coords (0.5 = 1/4 screen height)\nconst float ANIMATION_START_OFFSET = 0.0;        // Start the ripple slightly progressed (0.0 - 1.0)\nvec4 COLOR = vec4(0.35, 0.36, 0.44, 1.0); // change to iCurrentCursorColor for your cursor's color\nconst float CURSOR_WIDTH_CHANGE_THRESHOLD = 0.5; // Triggers ripple if cursor width changes by this fraction\nconst float BLUR = 3.0;                    // Blur level in pixels\n\n// Easing functions\nfloat easeOutQuad(float t) {\n    return 1.0 - (1.0 - t) * (1.0 - t);\n}\nfloat easeInOutQuad(float t) {\n    return t < 0.5 ? 2.0 * t * t : 1.0 - pow(-2.0 * t + 2.0, 2.0) / 2.0;\n}\nfloat easeOutCubic(float t) {\n    return 1.0 - pow(1.0 - t, 3.0);\n}\nfloat easeOutQuart(float t) {\n    return 1.0 - pow(1.0 - t, 4.0);\n}\nfloat easeOutQuint(float t) {\n    return 1.0 - pow(1.0 - t, 5.0);\n}\nfloat easeOutExpo(float t) {\n    return t == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * t);\n}\nfloat easeOutCirc(float t) {\n    return sqrt(1.0 - pow(t - 1.0, 2.0));\n}\nfloat easeOutSine(float t) {\n    return sin((t * 3.1415916) / 2.0);\n}\nfloat easeOutElastic(float t) {\n    const float c4 = (2.0 * 3.1415916) / 3.0;\n    return t == 0.0 ? 0.0 : t == 1.0 ? 1.0 : pow(2.0, -10.0 * t) * sin((t * 10.0 - 0.75) * c4) + 1.0;\n}\nfloat easeOutBounce(float t) {\n    const float n1 = 7.5625;\n    const float d1 = 2.75;\n    if (t < 1.0 / d1) {\n        return n1 * t * t;\n    } else if (t < 2.0 / d1) {\n        return n1 * (t -= 1.5 / d1) * t + 0.75;\n    } else if (t < 2.5 / d1) {\n        return n1 * (t -= 2.25 / d1) * t + 0.9375;\n    } else {\n        return n1 * (t -= 2.625 / d1) * t + 0.984375;\n    }\n}\nfloat easeOutBack(float t) {\n    const float c1 = 1.70158;\n    const float c3 = c1 + 1.0;\n    return 1.0 + c3 * pow(t - 1.0, 3.0) + c1 * pow(t - 1.0, 2.0);\n}\n\n// Pulse fade functions\nfloat smoothstepPulse(float t) {\n    return 4.0 * t * (1.0 - t);\n}\nfloat easeOutPulse(float t) {\n    return t * (2.0 - t);\n}\nfloat powerCurvePulse(float t) {\n    float x = t * 2.0 - 1.0;\n    return 1.0 - x * x;\n}\nfloat doubleSmoothstepPulse(float t) {\n    return smoothstep(0.0, 0.5, t) * (1.0 - smoothstep(0.5, 1.0, t));\n}\nfloat exponentialDecayPulse(float t) {\n    return exp(-3.0 * t) * sin(t * 3.1415916);\n}\nfloat sinPulse(float t) {\n    return sin(t * 3.1415916);\n}\n\nvec2 normalize(vec2 value, float isPosition) {\n    return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;\n}\n\nfloat getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b){\n    vec2 d = abs(p - xy) - b;\n    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord){\n    #if !defined(WEB)\n    fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);\n    #endif\n\n    // Normalization & setup (-1 to 1 coords)\n    vec2 vu = normalize(fragCoord, 1.);\n    vec2 offsetFactor = vec2(-.5, 0.5);\n\n    vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));\n    vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));\n\n    vec2 centerCC = currentCursor.xy - (currentCursor.zw * offsetFactor);\n\n    float cellWidth = max(currentCursor.z, previousCursor.z); // width of the 'block' cursor\n    \n    // check for significant width change\n    float widthChange = abs(currentCursor.z - previousCursor.z);\n    float widthThresholdNorm = cellWidth * CURSOR_WIDTH_CHANGE_THRESHOLD;\n    float isModeChange = step(widthThresholdNorm, widthChange);\n\n\n    // ANIMATION\n    float rippleProgress = (iTime - iTimeCursorChange) / DURATION + ANIMATION_START_OFFSET;\n    // don't clamp yet; we need to know if it's > 1.0 (finished)\n    float isAnimating = 1.0 - step(1.0, rippleProgress); // progress < 1.0 ? 1.0: 0.0\n    \n    // WHY NOT BRANCHLESS??? here ya go:\n    // because we NEVER have divergence, even in this if/else branchfull logic\n    // why? because its UNIFORM branching (ie, all fragments take the same path) which modern GPUs handles efficiently\n    // its far more efficient than calculating the ripple EVERY FRAME even when not needed(branchless)\n    if (isModeChange > 0.0 && isAnimating > 0.0) {\n        // float easedProgress = rippleProgress;\n        // float easedProgress = easeOutQuad(rippleProgress);\n        // float easedProgress = easeInOutQuad(rippleProgress);\n        // float easedProgress = easeOutCubic(rippleProgress);\n        // float easedProgress = easeOutQuart(rippleProgress);\n        // float easedProgress = easeOutQuint(rippleProgress);\n        // float easedProgress = easeOutExpo(rippleProgress);\n        float easedProgress = easeOutCirc(rippleProgress);\n        // float easedProgress = easeOutSine(rippleProgress);\n        // float easedProgress = easeOutBack(rippleProgress);\n\n        // easedProgress = clamp(easedProgress, 0.0, 1.0);\n\n        // RIPPLE CALCULATION\n        float rippleExpansion = easedProgress * MAX_SIZE;\n        \n        // float fade = 1.0; // no fade\n        // float fade = 1.0 - easedProgress; // linear fade\n        // float fade = 1.0 - smoothstepPulse(rippleProgress);\n        float fade = 1.0 - easeOutPulse(rippleProgress);\n        // float fade = 1.0 - powerCurvePulse(rippleProgress);\n        // float fade = doubleSmoothstepPulse(rippleProgress);\n        // float fade = exponentialDecayPulse(rippleProgress);\n        // float fade = sinPulse(rippleProgress);\n        \n        vec2 halfSizeCC = vec2(currentCursor.z, currentCursor.w) * 0.5 + vec2(rippleExpansion);\n        float sdfRectRing = getSdfRectangle(vu, centerCC, halfSizeCC);\n        \n        // Antialias (1-pixel width in normalized coords)\n        float antiAliasSize = normalize(vec2(BLUR, BLUR), 0.0).x;\n        float ripple = (1.0 - smoothstep(-antiAliasSize, antiAliasSize, sdfRectRing)) * fade;\n        // Apply ripple effect\n        fragColor = mix(fragColor, COLOR, ripple * COLOR.a);\n    }\n    // else: do nothing, keep original fragColor\n}\n"
  },
  {
    "path": "ripple_cursor.glsl",
    "content": "// CONFIGURATION\nconst float DURATION = 0.15;               // How long the ripple animates (seconds)\nconst float MAX_RADIUS = 0.05;             // Max radius in normalized coords (0.5 = 1/4 screen height)\nconst float RING_THICKNESS = 0.02;             // Ring width in normalized coords\nconst float CURSOR_WIDTH_CHANGE_THRESHOLD = 0.5; // Triggers ripple if cursor width changes by this fraction\nvec4 COLOR = vec4(0.35, 0.36, 0.44, 1.0); // change to iCurrentCursorColor for your cursor's color\nconst float BLUR = 3.0;                    // Blur level in pixels\nconst float ANIMATION_START_OFFSET = 0.0;        // Start the ripple slightly progressed (0.0 - 1.0)\n\n\n// Easing functions\nfloat easeOutQuad(float t) {\n    return 1.0 - (1.0 - t) * (1.0 - t);\n}\nfloat easeInOutQuad(float t) {\n    return t < 0.5 ? 2.0 * t * t : 1.0 - pow(-2.0 * t + 2.0, 2.0) / 2.0;\n}\nfloat easeOutCubic(float t) {\n    return 1.0 - pow(1.0 - t, 3.0);\n}\nfloat easeOutQuart(float t) {\n    return 1.0 - pow(1.0 - t, 4.0);\n}\nfloat easeOutQuint(float t) {\n    return 1.0 - pow(1.0 - t, 5.0);\n}\nfloat easeOutExpo(float t) {\n    return t == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * t);\n}\nfloat easeOutCirc(float t) {\n    return sqrt(1.0 - pow(t - 1.0, 2.0));\n}\nfloat easeOutSine(float t) {\n    return sin((t * 3.1415916) / 2.0);\n}\nfloat easeOutElastic(float t) {\n    const float c4 = (2.0 * 3.1415916) / 3.0;\n    return t == 0.0 ? 0.0 : t == 1.0 ? 1.0 : pow(2.0, -10.0 * t) * sin((t * 10.0 - 0.75) * c4) + 1.0;\n}\nfloat easeOutBounce(float t) {\n    const float n1 = 7.5625;\n    const float d1 = 2.75;\n    if (t < 1.0 / d1) {\n        return n1 * t * t;\n    } else if (t < 2.0 / d1) {\n        return n1 * (t -= 1.5 / d1) * t + 0.75;\n    } else if (t < 2.5 / d1) {\n        return n1 * (t -= 2.25 / d1) * t + 0.9375;\n    } else {\n        return n1 * (t -= 2.625 / d1) * t + 0.984375;\n    }\n}\nfloat easeOutBack(float t) {\n    const float c1 = 1.70158;\n    const float c3 = c1 + 1.0;\n    return 1.0 + c3 * pow(t - 1.0, 3.0) + c1 * pow(t - 1.0, 2.0);\n}\n\n// Pulse fade functions\nfloat easeOutPulse(float t) {\n    return t * (2.0 - t);\n}\nfloat exponentialDecayPulse(float t) {\n    return exp(-3.0 * t) * sin(t * 3.1415916);\n}\n\nvec2 normalize(vec2 value, float isPosition) {\n    return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord){\n    #if !defined(WEB)\n    fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);\n    #endif\n\n    // Normalization & setup (-1 to 1 coords)\n    vec2 vu = normalize(fragCoord, 1.);\n    vec2 offsetFactor = vec2(-.5, 0.5);\n\n    vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));\n    vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));\n\n    vec2 centerCC = currentCursor.xy - (currentCursor.zw * offsetFactor);\n\n    float cellWidth = max(currentCursor.z, previousCursor.z); // width of the 'block' cursor\n    \n    // check for significant width change\n    float widthChange = abs(currentCursor.z - previousCursor.z);\n    float widthThresholdNorm = cellWidth * CURSOR_WIDTH_CHANGE_THRESHOLD;\n    float isModeChange = step(widthThresholdNorm, widthChange);\n\n\n    // ANIMATION\n    float rippleProgress = (iTime - iTimeCursorChange) / DURATION + ANIMATION_START_OFFSET;\n    // don't clamp yet; we need to know if it's > 1.0 (finished)\n     float isAnimating = 1.0 - step(1.0, rippleProgress); // progress < 1.0 ? 1.0: 0.0\n     \n     if (isModeChange > 0.0 && isAnimating > 0.0) {\n        // Apply easing to progress\n        // float easedProgress = rippleProgress;\n        // float easedProgress = easeOutQuad(rippleProgress);\n        // float easedProgress = easeInOutQuad(rippleProgress);\n        // float easedProgress = easeOutCubic(rippleProgress);\n        // float easedProgress = easeOutQuart(rippleProgress);\n        // float easedProgress = easeOutQuint(rippleProgress);\n        // float easedProgress = easeOutExpo(rippleProgress);\n        float easedProgress = easeOutCirc(rippleProgress);\n        // float easedProgress = easeOutSine(rippleProgress);\n        // float easedProgress = easeOutBack(rippleProgress);\n\n        // RIPPLE CALCULATION\n        float rippleRadius = easedProgress * MAX_RADIUS;\n        \n        // float fade = 1.0; // no fade\n        // float fade = 1.0 - easedProgress; // linear fade\n        float fade = 1.0 - easeOutPulse(rippleProgress);\n        // float fade = 1.0 - exponentialDecayPulse(rippleProgress);\n        \n        // Calculate distance from frag to cursor center\n        float dist = distance(vu, centerCC);\n        \n        float sdfRing = abs(dist - rippleRadius) - RING_THICKNESS * 0.5;\n        \n        // Antialias (1-pixel width in normalized coords)\n        float antiAliasSize = normalize(vec2(BLUR, BLUR), 0.0).x;\n        float ripple = (1.0 - smoothstep(-antiAliasSize, antiAliasSize, sdfRing)) * fade;\n        \n        // Apply ripple effect\n        fragColor = mix(fragColor, COLOR, ripple * COLOR.a);\n    }\n    // else: do nothing, keep original fragColor\n}\n"
  },
  {
    "path": "ripple_rectangle_cursor.glsl",
    "content": "// CONFIGURATION\nconst float DURATION = 0.15;               // How long the ripple animates (seconds)\nconst float MAX_SIZE = 0.05;             // Max radius in normalized coords (0.5 = 1/4 screen height)\nconst float RING_THICKNESS = 0.02;             // Ring width in normalized coords\nconst float CURSOR_WIDTH_CHANGE_THRESHOLD = 0.5; // Triggers ripple if cursor width changes by this fraction\nvec4 COLOR = vec4(0.35, 0.36, 0.44, 1.0); // change to iCurrentCursorColor for your cursor's color\nconst float BLUR = 1.0;                    // Blur level in pixels\nconst float ANIMATION_START_OFFSET = 0.0;        // Start the ripple slightly progressed (0.0 - 1.0)\n\n\n// Easing functions\nfloat easeOutQuad(float t) {\n    return 1.0 - (1.0 - t) * (1.0 - t);\n}\nfloat easeInOutQuad(float t) {\n    return t < 0.5 ? 2.0 * t * t : 1.0 - pow(-2.0 * t + 2.0, 2.0) / 2.0;\n}\nfloat easeOutCubic(float t) {\n    return 1.0 - pow(1.0 - t, 3.0);\n}\nfloat easeOutQuart(float t) {\n    return 1.0 - pow(1.0 - t, 4.0);\n}\nfloat easeOutQuint(float t) {\n    return 1.0 - pow(1.0 - t, 5.0);\n}\nfloat easeOutExpo(float t) {\n    return t == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * t);\n}\nfloat easeOutCirc(float t) {\n    return sqrt(1.0 - pow(t - 1.0, 2.0));\n}\nfloat easeOutSine(float t) {\n    return sin((t * 3.1415916) / 2.0);\n}\nfloat easeOutElastic(float t) {\n    const float c4 = (2.0 * 3.1415916) / 3.0;\n    return t == 0.0 ? 0.0 : t == 1.0 ? 1.0 : pow(2.0, -10.0 * t) * sin((t * 10.0 - 0.75) * c4) + 1.0;\n}\nfloat easeOutBounce(float t) {\n    const float n1 = 7.5625;\n    const float d1 = 2.75;\n    if (t < 1.0 / d1) {\n        return n1 * t * t;\n    } else if (t < 2.0 / d1) {\n        return n1 * (t -= 1.5 / d1) * t + 0.75;\n    } else if (t < 2.5 / d1) {\n        return n1 * (t -= 2.25 / d1) * t + 0.9375;\n    } else {\n        return n1 * (t -= 2.625 / d1) * t + 0.984375;\n    }\n}\nfloat easeOutBack(float t) {\n    const float c1 = 1.70158;\n    const float c3 = c1 + 1.0;\n    return 1.0 + c3 * pow(t - 1.0, 3.0) + c1 * pow(t - 1.0, 2.0);\n}\n\n// Pulse fade functions\nfloat easeOutPulse(float t) {\n    return t * (2.0 - t);\n}\nfloat exponentialDecayPulse(float t) {\n    return exp(-3.0 * t) * sin(t * 3.1415916);\n}\n\nvec2 normalize(vec2 value, float isPosition) {\n    return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;\n}\n\nfloat getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b){\n    vec2 d = abs(p - xy) - b;\n    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord){\n    #if !defined(WEB)\n    fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);\n    #endif\n\n    // Normalization & setup (-1 to 1 coords)\n    vec2 vu = normalize(fragCoord, 1.);\n    vec2 offsetFactor = vec2(-.5, 0.5);\n\n    vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));\n    vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));\n\n    vec2 centerCC = currentCursor.xy - (currentCursor.zw * offsetFactor);\n\n    float cellWidth = max(currentCursor.z, previousCursor.z); // width of the 'block' cursor\n    \n    // check for significant width change\n    float widthChange = abs(currentCursor.z - previousCursor.z);\n    float widthThresholdNorm = cellWidth * CURSOR_WIDTH_CHANGE_THRESHOLD;\n    float isModeChange = step(widthThresholdNorm, widthChange);\n\n\n    // ANIMATION\n    float rippleProgress = (iTime - iTimeCursorChange) / DURATION + ANIMATION_START_OFFSET;\n    // don't clamp yet; we need to know if it's > 1.0 (finished)\n     float isAnimating = 1.0 - step(1.0, rippleProgress); // progress < 1.0 ? 1.0: 0.0\n     \n     if (isModeChange > 0.0 && isAnimating > 0.0) {\n        // Apply easing to progress\n        // float easedProgress = rippleProgress;\n        // float easedProgress = easeOutQuad(rippleProgress);\n        // float easedProgress = easeInOutQuad(rippleProgress);\n        // float easedProgress = easeOutCubic(rippleProgress);\n        // float easedProgress = easeOutQuart(rippleProgress);\n        // float easedProgress = easeOutQuint(rippleProgress);\n        // float easedProgress = easeOutExpo(rippleProgress);\n        float easedProgress = easeOutCirc(rippleProgress);\n        // float easedProgress = easeOutSine(rippleProgress);\n        // float easedProgress = easeOutBack(rippleProgress);\n\n        // RIPPLE CALCULATION\n        float rippleExpansion = easedProgress * MAX_SIZE;\n        \n        // float fade = 1.0; // no fade\n        // float fade = 1.0 - easedProgress; // linear fade\n        float fade = 1.0 - easeOutPulse(rippleProgress);\n        // float fade = 1.0 - exponentialDecayPulse(rippleProgress);\n        \n        // Calculate distance from frag to cursor center\n        // float dist = distance(vu, centerCC);\n        \n        // float sdfRing = abs(dist - rippleExpansion) - RING_THICKNESS * 0.5;\n        vec2 halfSizeCC = vec2(currentCursor.z, currentCursor.w) * 0.5 + vec2(rippleExpansion);\n        float sdfRectRing = abs(getSdfRectangle(vu, centerCC, halfSizeCC)) - RING_THICKNESS * 0.5;\n        // Antialias (1-pixel width in normalized coords)\n        float antiAliasSize = normalize(vec2(BLUR, BLUR), 0.0).x;\n        float ripple = (1.0 - smoothstep(-antiAliasSize, antiAliasSize, sdfRectRing)) * fade;\n\n        // Apply ripple effect\n        fragColor = mix(fragColor, COLOR, ripple * COLOR.a);\n    }\n    // else: do nothing, keep original fragColor\n}\n"
  },
  {
    "path": "sonic_boom_cursor.glsl",
    "content": "// CONFIGURATION\nconst float DURATION = 0.15;               // How long the ripple animates (seconds)\nconst float MAX_RADIUS = 0.06;             // Max radius in normalized coords (0.5 = 1/4 screen height)\nconst float ANIMATION_START_OFFSET = 0.0;        // Start the ripple slightly progressed (0.0 - 1.0)\nvec4 COLOR = vec4(0.35, 0.36, 0.44, 1.0); // change to iCurrentCursorColor for your cursor's color\nconst float CURSOR_WIDTH_CHANGE_THRESHOLD = 0.5; // Triggers ripple if cursor width changes by this fraction\nconst float BLUR = 3.0;                    // Blur level in pixels\n\n\n// Easing functions\nfloat easeOutQuad(float t) {\n    return 1.0 - (1.0 - t) * (1.0 - t);\n}\nfloat easeInOutQuad(float t) {\n    return t < 0.5 ? 2.0 * t * t : 1.0 - pow(-2.0 * t + 2.0, 2.0) / 2.0;\n}\nfloat easeOutCubic(float t) {\n    return 1.0 - pow(1.0 - t, 3.0);\n}\nfloat easeOutQuart(float t) {\n    return 1.0 - pow(1.0 - t, 4.0);\n}\nfloat easeOutQuint(float t) {\n    return 1.0 - pow(1.0 - t, 5.0);\n}\nfloat easeOutExpo(float t) {\n    return t == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * t);\n}\nfloat easeOutCirc(float t) {\n    return sqrt(1.0 - pow(t - 1.0, 2.0));\n}\nfloat easeOutSine(float t) {\n    return sin((t * 3.1415916) / 2.0);\n}\nfloat easeOutElastic(float t) {\n    const float c4 = (2.0 * 3.1415916) / 3.0;\n    return t == 0.0 ? 0.0 : t == 1.0 ? 1.0 : pow(2.0, -10.0 * t) * sin((t * 10.0 - 0.75) * c4) + 1.0;\n}\nfloat easeOutBounce(float t) {\n    const float n1 = 7.5625;\n    const float d1 = 2.75;\n    if (t < 1.0 / d1) {\n        return n1 * t * t;\n    } else if (t < 2.0 / d1) {\n        return n1 * (t -= 1.5 / d1) * t + 0.75;\n    } else if (t < 2.5 / d1) {\n        return n1 * (t -= 2.25 / d1) * t + 0.9375;\n    } else {\n        return n1 * (t -= 2.625 / d1) * t + 0.984375;\n    }\n}\nfloat easeOutBack(float t) {\n    const float c1 = 1.70158;\n    const float c3 = c1 + 1.0;\n    return 1.0 + c3 * pow(t - 1.0, 3.0) + c1 * pow(t - 1.0, 2.0);\n}\n\n// Pulse fade functions\nfloat smoothstepPulse(float t) {\n    return 4.0 * t * (1.0 - t);\n}\nfloat easeOutPulse(float t) {\n    return t * (2.0 - t);\n}\nfloat powerCurvePulse(float t) {\n    float x = t * 2.0 - 1.0;\n    return 1.0 - x * x;\n}\nfloat doubleSmoothstepPulse(float t) {\n    return smoothstep(0.0, 0.5, t) * (1.0 - smoothstep(0.5, 1.0, t));\n}\nfloat exponentialDecayPulse(float t) {\n    return exp(-3.0 * t) * sin(t * 3.1415916);\n}\nfloat sinPulse(float t) {\n    return sin(t * 3.1415916);\n}\n\nvec2 normalize(vec2 value, float isPosition) {\n    return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord){\n    #if !defined(WEB)\n    fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);\n    #endif\n\n    // Normalization & setup (-1 to 1 coords)\n    vec2 vu = normalize(fragCoord, 1.);\n    vec2 offsetFactor = vec2(-.5, 0.5);\n\n    vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));\n    vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));\n\n    vec2 centerCC = currentCursor.xy - (currentCursor.zw * offsetFactor);\n\n    float cellWidth = max(currentCursor.z, previousCursor.z); // width of the 'block' cursor\n    \n    // check for significant width change\n    float widthChange = abs(currentCursor.z - previousCursor.z);\n    float widthThresholdNorm = cellWidth * CURSOR_WIDTH_CHANGE_THRESHOLD;\n    float isModeChange = step(widthThresholdNorm, widthChange);\n\n\n    // ANIMATION\n    float rippleProgress = (iTime - iTimeCursorChange) / DURATION + ANIMATION_START_OFFSET;\n    // don't clamp yet; we need to know if it's > 1.0 (finished)\n     float isAnimating = 1.0 - step(1.0, rippleProgress); // progress < 1.0 ? 1.0: 0.0\n     \n     if (isModeChange > 0.0 && isAnimating > 0.0) {\n        // float easedProgress = rippleProgress;\n        // float easedProgress = easeOutQuad(rippleProgress);\n        // float easedProgress = easeInOutQuad(rippleProgress);\n        // float easedProgress = easeOutCubic(rippleProgress);\n        // float easedProgress = easeOutQuart(rippleProgress);\n        // float easedProgress = easeOutQuint(rippleProgress);\n        // float easedProgress = easeOutExpo(rippleProgress);\n        float easedProgress = easeOutCirc(rippleProgress);\n        // float easedProgress = easeOutSine(rippleProgress);\n        // float easedProgress = easeOutBack(rippleProgress);\n\n        // easedProgress = clamp(easedProgress, 0.0, 1.0);\n\n        // RIPPLE CALCULATION\n        float rippleRadius = easedProgress * MAX_RADIUS;\n        \n        // float fade = 1.0; // no fade\n        // float fade = 1.0 - easedProgress; // linear fade\n        // float fade = 1.0 - smoothstepPulse(rippleProgress);\n        float fade = 1.0 - easeOutPulse(rippleProgress);\n        // float fade = 1.0 - powerCurvePulse(rippleProgress);\n        // float fade = doubleSmoothstepPulse(rippleProgress);\n        // float fade = exponentialDecayPulse(rippleProgress);\n        // float fade = sinPulse(rippleProgress);\n        \n        // Calculate distance from frag to cursor center\n        float dist = distance(vu, centerCC);\n        \n        float sdfCircle = dist - rippleRadius;\n        \n        // Antialias (1-pixel width in normalized coords)\n        float antiAliasSize = normalize(vec2(BLUR, BLUR), 0.0).x;\n        float ripple = (1.0 - smoothstep(-antiAliasSize, antiAliasSize, sdfCircle)) * fade;\n        \n        // Apply ripple effect\n        fragColor = mix(fragColor, COLOR, ripple * COLOR.a);\n    }\n}\n"
  }
]