
(function() {
    
    var _origArc = CanvasRenderingContext2D.prototype.arc;
    CanvasRenderingContext2D.prototype.arc = function(x, y, r, s, e, cc) {
        _origArc.call(this, x, y, Math.max(0, r), s, e, cc);
    };
    var _origGrad = CanvasRenderingContext2D.prototype.createRadialGradient;
    CanvasRenderingContext2D.prototype.createRadialGradient = function(x0, y0, r0, x1, y1, r1) {
        return _origGrad.call(this, x0, y0, Math.max(0, r0), x1, y1, Math.max(0, r1));
    };
'use strict';

function seededRandom(seed) { var x = Math.sin(seed) * 10000; return x - Math.floor(x); }

var _impMouseX = 0, _impMouseY = 0, _impMouseTX = 0, _impMouseTY = 0;
var _impTiltSrc = 'mouse'; 
function _impMouseTick() {
    var t = window._impulseTilt;
    if (t) {
        if (isFinite(t.x)) _impMouseTX = t.x;
        if (isFinite(t.y)) _impMouseTY = t.y;
    }
    var dx = _impMouseTX - _impMouseX;
    var dy = _impMouseTY - _impMouseY;
    if (isFinite(dx)) _impMouseX += dx * 0.08;
    if (isFinite(dy)) _impMouseY += dy * 0.08;
    if (!isFinite(_impMouseX)) _impMouseX = 0;
    if (!isFinite(_impMouseY)) _impMouseY = 0;
}

function buildConicGradient(ctx, cx, cy, startAngle, c1, c2) {
    if (!ctx.createConicGradient) return null;
    try {
        var g = ctx.createConicGradient(startAngle, cx, cy);
        g.addColorStop(0, c1);
        g.addColorStop(0.5, c2);
        g.addColorStop(1, c1);
        return g;
    } catch (_gErr) {
        return null;
    }
}

function _parseColor(c) {
    if (!c) return null;
    if (c.charAt(0) === '#') {
        var hex = c.slice(1);
        if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
        return { r: parseInt(hex.substr(0,2),16), g: parseInt(hex.substr(2,2),16), b: parseInt(hex.substr(4,2),16) };
    }
    var m = c.match(/\d+/g);
    return m ? { r: +m[0], g: +m[1], b: +m[2] } : null;
}

function _hasVisibleAlpha(c) {
    if (!c || c === 'transparent') return false;
    var m = c.match(/rgba?\([^)]+\)/);
    if (m) {
        var parts = m[0].match(/[\d.]+/g);
        if (parts && parts.length >= 4 && parseFloat(parts[3]) === 0) return false;
    }
    return true;
}

function _colorAlpha(c) {
    if (!c || c === 'transparent') return c === 'transparent' ? 0 : 1;
    var m = c.match(/rgba?\(([^)]+)\)/);
    if (!m) return 1; 
    var parts = m[1].split(',');
    if (parts.length < 4) return 1;
    var a = parseFloat(parts[3]);
    return isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
}

function mixColors(c1, c2, t) {
    var a = _parseColor(c1), b = _parseColor(c2);
    if (!a || !b) return c1;
    var r = Math.round(a.r + (b.r - a.r) * t);
    var g = Math.round(a.g + (b.g - a.g) * t);
    var bl = Math.round(a.b + (b.b - a.b) * t);
    return 'rgb(' + r + ',' + g + ',' + bl + ')';
}

var _blurCanvas, _blurCtx;
function getBlurCanvas(w, h) {
    var s = 0.5;
    var bw = Math.ceil(w * s), bh = Math.ceil(h * s);
    if (!_blurCanvas || _blurCanvas.width !== bw || _blurCanvas.height !== bh) {
        _blurCanvas = document.createElement('canvas');
        _blurCanvas.width = bw;
        _blurCanvas.height = bh;
        _blurCtx = _blurCanvas.getContext('2d');
    } else {
        _blurCtx.clearRect(0, 0, bw, bh);
    }
    _blurCtx.filter = 'none';
    _blurCtx.globalAlpha = 1;
    return { canvas: _blurCanvas, ctx: _blurCtx, scale: s };
}

function compositeBlur(ctx, buf, w, h, blurAmount) {
    var bl = Math.min(blurAmount, 6);
    ctx.save();
    ctx.filter = 'blur(' + bl.toFixed(1) + 'px)';
    ctx.drawImage(buf.canvas, 0, 0, buf.canvas.width, buf.canvas.height, 0, 0, w, h);
    ctx.filter = 'none';
    ctx.restore();
}

function drawEffect(ctx, sourceLayer, w, h, time) {

    

    
    var layer = sourceLayer;
    if (typeof sourceLayer.speed === 'number' && sourceLayer.speed > 0) {
        layer = Object.assign({}, sourceLayer);
        layer.speed = sourceLayer.speed * 0.3;
    }
    var cx = w * (layer.origin_x / 100);
    var cy = h * (layer.origin_y / 100);
    var maxR = Math.sqrt(Math.max(cx, w-cx)**2 + Math.max(cy, h-cy)**2) * 1.5;
    function colorAlpha(c,a){if(c.charAt(0)==="#"){var hex=c.slice(1);if(hex.length===3)hex=hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];return "rgba("+parseInt(hex.substr(0,2),16)+","+parseInt(hex.substr(2,2),16)+","+parseInt(hex.substr(4,2),16)+","+a+")";}return c;}
    function hexToRgb(c){var hex=c.slice(1);if(hex.length===3)hex=hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];return[parseInt(hex.substr(0,2),16),parseInt(hex.substr(2,2),16),parseInt(hex.substr(4,2),16)];}
    function varyColor(c,variation,seed){if(!variation)return c;var rgb=hexToRgb(c);var v=(variation/100)*360;var rShift=(seededRandom(seed)-0.5)*v;var gShift=(seededRandom(seed+1000)-0.5)*v;var bShift=(seededRandom(seed+2000)-0.5)*v;var r=Math.max(0,Math.min(255,rgb[0]+rShift));var g=Math.max(0,Math.min(255,rgb[1]+gShift));var b=Math.max(0,Math.min(255,rgb[2]+bShift));return "rgb("+Math.round(r)+","+Math.round(g)+","+Math.round(b)+")";}
    ctx.strokeStyle = layer.color; ctx.fillStyle = layer.color; ctx.lineWidth = layer.thickness || 2;
    switch (layer.effect) {
        case "rings":
            var ringSpacing=layer.spacing||50,ringCount=Math.ceil(maxR/ringSpacing)+1;
            var thickDecay=(layer.thickness_decay||0)/100,opacDecay=(layer.opacity_decay||0)/100;
            var baseThick=layer.thickness||2,erColorVar=layer.color_variation||0;
            var erCycle=ringCount*ringSpacing;
            var erGradEnabled=_hasVisibleAlpha(layer.color_2),erGrad=null;
            if(erGradEnabled){
                var erAngle=_impMouseX*((layer.gradient_mouse||0)/100)*Math.PI*2;
                erGrad=buildConicGradient(ctx,cx,cy,erAngle,layer.color,layer.color_2||"#ffffff");
            }
            for(var i=0;i<ringCount;i++){var r=((time*60*layer.speed+i*(erCycle/ringCount))%erCycle);if(r>maxR)continue;var progress=r/maxR;ctx.globalAlpha=Math.max(0.01,Math.pow(1-progress,1+opacDecay*4));ctx.lineWidth=Math.max(0.5,baseThick*Math.pow(1-progress,thickDecay*3));ctx.strokeStyle=erGrad?erGrad:(erColorVar?varyColor(layer.color,erColorVar,i*137):layer.color);ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.stroke();}ctx.globalAlpha=1;ctx.lineWidth=baseThick;ctx.strokeStyle=layer.color;break;
        case "ring_trace":

            

            

            

            

            

            

            

            

            

            

            

            

            

            

            var rtDevice = w < 768 ? 'mobile' : (w < 1024 ? 'tablet' : 'desktop');
            function rtRead(key, fallback) {
                if (rtDevice !== 'desktop') {
                    var dk = layer[key + '_' + rtDevice];
                    if (dk !== undefined && dk !== '' && dk !== null) return dk;
                }
                var bk = layer[key];
                if (bk !== undefined && bk !== '' && bk !== null) return bk;
                return fallback;
            }
            function rtReadBool(key, fallback) {
                var v = rtRead(key, fallback);
                if (v === true || v === 'true' || v === 1 || v === '1') return true;
                if (v === false || v === 'false' || v === 0 || v === '0') return false;
                return !!fallback;
            }
            var rtSpeed = +rtRead('speed', 1) || 1;
            var rtRadius0 = +rtRead('radius_px', 200);

            if ((!rtRadius0 || rtRadius0 <= 0) && layer.radius_pct != null) {
                rtRadius0 = Math.min(cx, cy, w - cx, h - cy) * (layer.radius_pct / 100);
            }
            if (!rtRadius0 || rtRadius0 <= 0) rtRadius0 = 200;
            var rtThick = +rtRead('thickness', 6) || 6;
            var rtOriginRad = ((+rtRead('origin_angle', 180)) * Math.PI) / 180;

            
            var rtCCW = false;
            var rtDirSign = 1;
            var rtHoldPct = (+rtRead('hold_pct', 100)) / 100;
            var rtLoop = rtReadBool('loop', true);
            var rtShowHead = rtReadBool('show_head', true);
            var rtStyle = rtRead('stroke_type', 'solid');
            if (rtStyle === 'brush') rtStyle = 'sketch'; 
            var rtGradSpin = (+rtRead('gradient_spin', 0)) / 100;
            var rtBrushSeed = layer.brush_seed || 7;
            var T_DRAW_RT = 1.5 / rtSpeed;
            var T_SNAP_RT = 0.12 / rtSpeed;
            var T_HOLD_RT = (1.5 * rtHoldPct) / rtSpeed;
            var T_FADE_RT = 1.5 / rtSpeed;
            var T_TOTAL_RT = T_DRAW_RT + T_SNAP_RT + T_HOLD_RT + T_FADE_RT;
            var rtT = rtLoop ? (time % T_TOTAL_RT) : Math.min(time, T_TOTAL_RT);
            var ANTI_FRAC_RT = 0.08;
            var ANTI_RAD_RT = (5 * Math.PI) / 180; 
            function easeOutBackRT(t) { var c = 1.70158; return 1 + (c + 1) * Math.pow(t - 1, 3) + c * Math.pow(t - 1, 2); }
            function easeOutCubicRT(t) { return 1 - Math.pow(1 - t, 3); }
            var rtTailA, rtTipA, rtScale = 1, rtIsDrawing = false, rtIsFading = false, rtSnapMix = 0;
            var rtAntiDotA = null; 
            if (rtT < T_DRAW_RT) {
                var rawProg = rtT / T_DRAW_RT;
                if (rawProg < ANTI_FRAC_RT) {

                    

                    

                    

                    
                    var antiProg = rawProg / ANTI_FRAC_RT;
                    var antiCurve = Math.sin(antiProg * Math.PI);
                    rtTipA = rtOriginRad;
                    rtTailA = rtOriginRad;
                    rtAntiDotA = rtOriginRad - rtDirSign * ANTI_RAD_RT * antiCurve;
                } else {
                    var drawProg = (rawProg - ANTI_FRAC_RT) / (1 - ANTI_FRAC_RT);
                    var drawEased = easeOutBackRT(Math.min(1, drawProg));
                    rtTipA = rtOriginRad + rtDirSign * 2 * Math.PI * drawEased;
                    rtTailA = rtOriginRad;
                }
                rtIsDrawing = true;
            } else if (rtT < T_DRAW_RT + T_SNAP_RT) {
                rtSnapMix = (rtT - T_DRAW_RT) / T_SNAP_RT;
                rtScale = 1 + 0.10 * Math.sin(rtSnapMix * Math.PI);
                rtTipA = rtOriginRad + rtDirSign * 2 * Math.PI;
                rtTailA = rtOriginRad;
            } else if (rtT < T_DRAW_RT + T_SNAP_RT + T_HOLD_RT) {
                rtTipA = rtOriginRad + rtDirSign * 2 * Math.PI;
                rtTailA = rtOriginRad;
            } else {

                

                

                var rtF = (rtT - T_DRAW_RT - T_SNAP_RT - T_HOLD_RT) / T_FADE_RT;
                var rtFEased = easeOutCubicRT(Math.min(1, rtF));
                rtTailA = rtOriginRad + rtDirSign * 2 * Math.PI * rtFEased;
                rtTipA  = rtOriginRad + rtDirSign * 2 * Math.PI;
                rtIsFading = true;
            }
            var rtRadius = rtRadius0 * rtScale;
            var rtWobble = rtIsDrawing ? (Math.sin(time * 9 * rtSpeed) * rtRadius0 * 0.012) : 0;
            var rtR = rtRadius + rtWobble;

            

            

            

            

            
            var rtBaseAlpha = _colorAlpha(layer.color);
            function _rtOpaque(c) {
                if (!c) return c;
                if (c.charAt(0) === '#') return c;
                var p = _parseColor(c);
                if (!p) return c;
                return 'rgb(' + p.r + ',' + p.g + ',' + p.b + ')';
            }
            var rtBaseColor = _rtOpaque(layer.color);
            var rtBaseColor2 = _rtOpaque(layer.color_2 || '#ffffff');
            var rtGrad = null;
            var rtGradEnabled = _hasVisibleAlpha(layer.color_2);
            if (rtGradEnabled) {
                var rtConicAngle = rtGradSpin > 0 ? (time * rtGradSpin * Math.PI * 2) : 0;
                rtGrad = buildConicGradient(ctx, cx, cy, rtConicAngle, rtBaseColor, rtBaseColor2);
            }
            ctx.save();
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';
            
            if (rtStyle === 'sketch') {
                
                var rtSweep = rtTipA - rtTailA;
                var rtSegW = (2 * Math.PI) / 90; 
                var rtSegs = Math.max(1, Math.floor(Math.abs(rtSweep) / rtSegW));
                ctx.strokeStyle = rtGrad || rtBaseColor;
                ctx.lineWidth = Math.max(1, rtThick * 0.6);
                ctx.globalAlpha = 0.85 * rtBaseAlpha;
                for (var rtSi = 0; rtSi < rtSegs; rtSi++) {
                    var rtSa0 = rtTailA + (rtSweep * rtSi) / rtSegs;
                    var rtSa1 = rtTailA + (rtSweep * (rtSi + 1)) / rtSegs;
                    var rtSJ0 = (seededRandom(rtBrushSeed + rtSi * 31) - 0.5) * rtThick * 0.8;
                    var rtSJ1 = (seededRandom(rtBrushSeed + rtSi * 31 + 7) - 0.5) * rtThick * 0.8;
                    ctx.beginPath();
                    ctx.moveTo(cx + (rtR + rtSJ0) * Math.cos(rtSa0), cy + (rtR + rtSJ0) * Math.sin(rtSa0));
                    ctx.lineTo(cx + (rtR + rtSJ1) * Math.cos(rtSa1), cy + (rtR + rtSJ1) * Math.sin(rtSa1));
                    ctx.stroke();
                }
            } else if (rtStyle === 'streak') {

                

                
                
                ctx.globalCompositeOperation = 'lighter';
                ctx.strokeStyle = rtGrad || rtBaseColor;
                for (var rtSP = 0; rtSP < 5; rtSP++) {
                    ctx.lineWidth = rtThick * (0.5 + rtSP * 0.9);
                    ctx.globalAlpha = (0.28 - rtSP * 0.05) * rtBaseAlpha;
                    ctx.beginPath();
                    ctx.arc(cx, cy, rtR, rtTailA, rtTipA, rtCCW);
                    ctx.stroke();
                }
                ctx.globalCompositeOperation = 'source-over';
            } else {
                
                var rtIsNeon = (rtStyle === 'neon');
                
                ctx.shadowColor = rtBaseColor;
                ctx.shadowBlur = rtThick * (rtIsNeon ? 5 : 1.8);
                ctx.strokeStyle = rtGrad || rtBaseColor;
                ctx.globalAlpha = (rtIsNeon ? 0.9 : 0.45) * rtBaseAlpha;
                ctx.lineWidth = rtThick * (rtIsNeon ? 1.4 : 1.5);
                ctx.beginPath();
                ctx.arc(cx, cy, rtR, rtTailA, rtTipA, rtCCW);
                ctx.stroke();
                
                ctx.shadowBlur = rtIsNeon ? rtThick * 2.5 : 0;
                ctx.globalAlpha = rtBaseAlpha;
                ctx.lineWidth = rtThick;
                ctx.beginPath();
                ctx.arc(cx, cy, rtR, rtTailA, rtTipA, rtCCW);
                ctx.stroke();

                
                
                if (rtIsNeon) {
                    var rtFlicker = 0.75 + 0.25 * Math.sin(time * 18 * rtSpeed) * Math.cos(time * 31 * rtSpeed);
                    ctx.shadowBlur = 0;
                    ctx.strokeStyle = '#ffffff';
                    ctx.globalAlpha = rtFlicker * rtBaseAlpha;
                    ctx.lineWidth = Math.max(1, rtThick * 0.32);
                    ctx.beginPath();
                    ctx.arc(cx, cy, rtR, rtTailA, rtTipA, rtCCW);
                    ctx.stroke();
                }
            }

            
            
            if (rtIsDrawing || rtIsFading) {
                ctx.shadowBlur = 0;
                ctx.globalCompositeOperation = (rtStyle === 'streak') ? 'lighter' : 'source-over';
                ctx.fillStyle = rtGrad || rtBaseColor;
                for (var rtTI = 1; rtTI <= 8; rtTI++) {
                    var rtPastT = rtT - rtTI * 0.022;
                    var rtPastA = null;
                    if (rtIsDrawing) {
                        if (rtPastT <= T_DRAW_RT * ANTI_FRAC_RT) break;
                        var rtPastRaw = rtPastT / T_DRAW_RT;
                        var rtPastDraw = (rtPastRaw - ANTI_FRAC_RT) / (1 - ANTI_FRAC_RT);
                        if (rtPastDraw < 0) continue;
                        var rtPastEased = easeOutBackRT(Math.min(1, rtPastDraw));
                        rtPastA = rtOriginRad + rtDirSign * 2 * Math.PI * rtPastEased;
                    } else {

                        
                        var rtPastFadeT = rtPastT - T_DRAW_RT - T_SNAP_RT - T_HOLD_RT;
                        if (rtPastFadeT <= 0) break;
                        var rtPastF = rtPastFadeT / T_FADE_RT;
                        var rtPastFEased = easeOutCubicRT(Math.min(1, rtPastF));
                        rtPastA = rtOriginRad + rtDirSign * 2 * Math.PI * rtPastFEased;
                    }
                    var rtTrailAlpha = (1 - rtTI / 9) * 0.55;
                    var rtTrailR = rtThick * (1.1 - rtTI / 14);
                    ctx.globalAlpha = rtTrailAlpha * rtBaseAlpha;
                    ctx.beginPath();
                    ctx.arc(cx + rtR * Math.cos(rtPastA), cy + rtR * Math.sin(rtPastA), rtTrailR, 0, Math.PI * 2);
                    ctx.fill();
                }
                ctx.globalCompositeOperation = 'source-over';
            }

            
            
            var rtSnapBurst = rtT >= T_DRAW_RT && rtT < T_DRAW_RT + T_SNAP_RT;
            if (rtShowHead && (rtIsDrawing || rtSnapBurst || rtIsFading)) {

                

                
                ctx.shadowColor = rtBaseColor;
                ctx.shadowBlur = rtThick * 4;
                ctx.fillStyle = '#ffffff';
                ctx.globalAlpha = rtBaseAlpha;
                var rtDotR = rtThick * 0.95;
                if (rtSnapBurst) {
                    
                    rtDotR = rtThick * (1 + 1.5 * Math.sin(rtSnapMix * Math.PI));
                }

                
                
                var rtLeadingA = (rtAntiDotA !== null)
                    ? rtAntiDotA
                    : (rtIsFading ? rtTailA : rtTipA);
                ctx.beginPath();
                ctx.arc(cx + rtR * Math.cos(rtLeadingA), cy + rtR * Math.sin(rtLeadingA), rtDotR, 0, Math.PI * 2);
                ctx.fill();
            }
            ctx.restore();
            break;

        
        case "concentric":

            

            
            
            var crCount=layer.ring_count||4,crRadius=(layer.radius||80)/100*maxR,crDash=layer.dash_length===0?0:(layer.dash_length||20),crColorVar=layer.color_variation||0;
            if(crDash>0){ctx.setLineDash([crDash,crDash*0.6]);}else{ctx.setLineDash([]);}
            var crMR=(layer.mouse_reactivity||0)/100;
            var crGM=(layer.gradient_mouse||0)/100;
            var crGradEnabled=_hasVisibleAlpha(layer.color_2),crGrad=null;
            if(crGradEnabled){

                

                var mx = isFinite(_impMouseX) ? _impMouseX : 0;
                var crAngle = mx * crGM * Math.PI * 2;
                if (!isFinite(crAngle)) crAngle = 0;
                var cacheKey = (crAngle*100|0) + "|" + (cx|0) + "," + (cy|0) + "|" + layer.color + "|" + layer.color_2;
                if (layer._cgKey !== cacheKey) {
                    layer._cgKey = cacheKey;
                    layer._cg = buildConicGradient(ctx, cx, cy, crAngle, layer.color, layer.color_2 || "#ffffff");
                }
                crGrad = layer._cg;
            }
            var crDepth=(layer.depth_3d||0)/100,crPlanarMax=40;
            var crBaseThick=layer.thickness||2;
            for(var i=0;i<crCount;i++){
                var r=(i+1)/crCount*crRadius;
                var rotDir=i%2===0?1:-1;

                var depthFactor=crCount>1?(i/(crCount-1)):0;

                

                
                var depthFade=1-depthFactor*crDepth*0.7;

                

                ctx.globalAlpha=(0.55+(i/crCount)*0.45)*depthFade;
                ctx.lineWidth=crBaseThick*(1-depthFactor*crDepth*0.3);
                ctx.strokeStyle=crGrad?crGrad:(crColorVar?varyColor(layer.color,crColorVar,i*317):layer.color);
                if(crDepth>0&&depthFactor>0){
                    ctx.shadowColor=layer.color;
                    ctx.shadowBlur=crDepth*depthFactor*12;
                }else{
                    ctx.shadowBlur=0;
                }

                
                var parallaxScale=1-depthFactor*crDepth*0.8;
                var _mx=isFinite(_impMouseX)?_impMouseX:0,_my=isFinite(_impMouseY)?_impMouseY:0;
                var crDx=_mx*crMR*crPlanarMax*parallaxScale;
                var crDy=_my*crMR*crPlanarMax*parallaxScale;
                ctx.save();
                ctx.translate(cx+crDx,cy+crDy);
                ctx.rotate(time*layer.speed*rotDir*(0.5+i*0.2));
                ctx.beginPath();ctx.arc(0,0,r,0,Math.PI*2);ctx.stroke();
                ctx.restore();
            }
            ctx.shadowBlur=0;ctx.setLineDash([]);ctx.globalAlpha=1;ctx.lineWidth=crBaseThick;ctx.strokeStyle=layer.color;break;

        

        

        

        
        case "caustics": {
            
            var cSpd=layer.speed*0.3,cBands=Math.max(3,Math.round((layer.spacing||50)/8)),cThick=(layer.thickness||3)*2,cSoft=(layer.softness!==undefined?layer.softness:60)/100;
            var rgb=hexToRgb(layer.color);
            for(var b=0;b<cBands;b++){
                var phase=b*1.7+time*cSpd*(0.8+b*0.15);
                var drift=Math.sin(time*cSpd*0.5+b*0.9)*w*0.15;
                ctx.beginPath();
                for(var x=0;x<=w;x+=4){
                    var nx=x/w;
                    var y1=Math.sin(nx*4+phase)*h*0.12;
                    var y2=Math.sin(nx*6.5+phase*1.3+b)*h*0.08;
                    var y3=Math.cos(nx*3.2+phase*0.7)*h*0.06;
                    var yy=cy+y1+y2+y3+Math.sin(b*2.1)*h*0.15+drift*Math.sin(nx*2);
                    x===0?ctx.moveTo(x,yy):ctx.lineTo(x,yy);
                }

                

                

                var coreWidth = cThick * (0.5 + 0.5 * Math.sin(time * 0.5 + b));
                if (cSoft > 0.01) {
                    ctx.lineWidth = coreWidth + cSoft * 16;
                    ctx.strokeStyle = colorAlpha(layer.color, 0.06);
                    ctx.stroke();
                    ctx.lineWidth = coreWidth + cSoft * 8;
                    ctx.strokeStyle = colorAlpha(layer.color, 0.10);
                    ctx.stroke();
                }
                ctx.lineWidth = coreWidth;
                ctx.strokeStyle = colorAlpha(layer.color, 0.55);
                ctx.stroke();
            }
            
            for(var i=0;i<12;i++){
                var ix=(seededRandom(i*31)*0.8+0.1)*w;
                var iy=cy+Math.sin(time*cSpd+i*1.9)*h*0.25+Math.cos(time*cSpd*0.7+i*2.3)*h*0.1;
                var iRad=8+Math.sin(time*2+i)*5;
                var ig=ctx.createRadialGradient(ix,iy,0,ix,iy,iRad*(1+cSoft));
                ig.addColorStop(0,colorAlpha(layer.color,0.15));
                ig.addColorStop(1,"rgba(0,0,0,0)");
                ctx.fillStyle=ig;ctx.fillRect(ix-iRad*2,iy-iRad*2,iRad*4,iRad*4);
            }
            break;
        }
        default:
            var dfCount=5,dfCycle=dfCount*50;
            for(var i=0;i<dfCount;i++){var r=((time*60*layer.speed+i*(dfCycle/dfCount))%dfCycle);if(r>maxR)continue;ctx.globalAlpha=Math.max(0,1-(r/maxR));ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.stroke();}ctx.globalAlpha=1;
    }
}

var activeAnimations = {};

function sectionGridOpts(layer) {
    layer = layer || {};
    return {
        effect: 'grid',
        transparentBg: true, 
        colors: [layer.color || '#a7c0e8', layer.color_2 || '#6f93d6'], 
        speed: Math.max(0.1, (parseFloat(layer.speed) || 4) * 0.2),
        intensity: (layer.size !== undefined ? Math.max(10, Math.min(250, parseFloat(layer.size))) : 100) / 100, 
        density: (layer.density !== undefined) ? parseFloat(layer.density) : 7,
        count: (layer.count !== undefined) ? parseFloat(layer.count) : 7,
        variation: (layer.variation !== undefined) ? parseFloat(layer.variation) : 3,
        borderWidth: (layer.border !== undefined) ? Math.max(0.5, Math.min(6, parseFloat(layer.border))) : 1.5,
        
        dotOpacity: _colorAlpha(layer.color),
        shapeOpacity: _colorAlpha(layer.color_2),
        sensitivity: Math.max(0, Math.min(10, (layer.mouse_sens !== undefined ? parseFloat(layer.mouse_sens) : 5))) * 0.2
    };
}

function initAnimation(wrapId, cfg, immediate) {
    var wrap = document.getElementById(wrapId);
    if (!wrap) return;
    var canvases = wrap.querySelectorAll("canvas");
    if (!canvases.length) return;

    if (activeAnimations[wrapId]) {
        activeAnimations[wrapId].cancelled = true;

        

        
        if (activeAnimations[wrapId]._lavaInsts) {
            activeAnimations[wrapId]._lavaInsts.forEach(function(inst) {
                if (inst && typeof inst.destroy === 'function') {
                    try { inst.destroy(); } catch (_) {}
                }
            });
        }
        
        if (activeAnimations[wrapId]._gridCanvases && window.Impulse3D) {
            activeAnimations[wrapId]._gridCanvases.forEach(function(cv) {
                try { window.Impulse3D.destroy(cv); } catch (_) {}
            });
        }

        

        

        

        

        
        
        Array.prototype.forEach.call(canvases, function(oldCv) {
            var newCv = oldCv.cloneNode(false);
            if (oldCv.parentNode) oldCv.parentNode.replaceChild(newCv, oldCv);
        });
        canvases = wrap.querySelectorAll("canvas");
    }
    var state = { cancelled: false, cfg: cfg };
    state._canvas = canvases[0] || null;   
    activeAnimations[wrapId] = state;

    function init() {
        if (state.cancelled) return;
        var w = cfg.fullscreen ? window.innerWidth : wrap.offsetWidth;
        var h = cfg.fullscreen ? window.innerHeight : wrap.offsetHeight;
        if (w < 10 || h < 10) { setTimeout(init, 100); return; }

        

        var dpr = 1;
        var contexts = [];     
        var lavaInsts = [];    
        var gridActive = [];   
        var gridCanvases = []; 
        canvases.forEach(function(canvas, i) {
            var layer = cfg.layers && cfg.layers[i];
            if (layer && layer.effect === 'lava' && typeof ImpulseMotionLava === 'function') {

                

                
                
                var _msRaw = (layer.mouse_sens !== undefined) ? parseFloat(layer.mouse_sens) : 5;

                
                
                var _shScale = (layer.shadow_scale !== undefined && layer.shadow_scale !== '')
                    ? Math.max(0, Math.min(1, parseFloat(layer.shadow_scale) / 100))
                    : 1;

                
                
                var _edgeRaw = (layer.edge_sharpness !== undefined && layer.edge_sharpness !== '')
                    ? parseFloat(layer.edge_sharpness) : 50;
                var _edgeExp = 1 + (Math.max(0, Math.min(100, _edgeRaw)) / 100) * 6;
                var inst = new ImpulseMotionLava(canvas, {
                    colors: [
                        layer.color   || '#16181c',
                        layer.color_2 || '#2d3580',
                        layer.color_3 || '#5040b0',
                        layer.color_4 || '#7038ff'
                    ],

                    speed: Math.max(0.05, (parseFloat(layer.speed) || 1) * 0.1),
                    mouseSensitivity: Math.max(0, _msRaw) * 0.1,
                    shadowScale: _shScale,
                    edgeSharpness: _edgeExp
                });
                inst.play();
                contexts.push(null);
                lavaInsts.push(inst);
                gridActive.push(false);
            } else if (layer && layer.effect === 'grid' && window.Impulse3D) {

                

                var _gInst = window.Impulse3D.init(canvas, sectionGridOpts(layer));
                contexts.push(null);
                lavaInsts.push(null);
                gridActive.push(!!_gInst);
                if (_gInst) gridCanvases.push(canvas);
            } else {
                canvas.width = w * dpr; canvas.height = h * dpr;
                var ctx = canvas.getContext("2d"); ctx.scale(dpr, dpr); contexts.push(ctx);
                lavaInsts.push(null);
                gridActive.push(false);
            }
        });
        
        state._lavaInsts = lavaInsts;
        state._gridCanvases = gridCanvases;

        function resizeCanvases(nw, nh) {
            w = nw; h = nh;
            canvases.forEach(function(canvas, i) {
                if (lavaInsts[i]) {

                    
                    try { lavaInsts[i].resize(); } catch (_) {}
                } else if (gridActive[i]) {

                    
                } else {
                    canvas.width = w * dpr; canvas.height = h * dpr;
                    contexts[i] = canvas.getContext("2d"); contexts[i].scale(dpr, dpr);
                }
            });
        }

        var startTime = performance.now() / 1000;
        var rafId = null;

        

        

        var _screenHost = (function() {

            if (cfg.fullscreen) return null;

            

            

            

            
            
            var screen = wrap.closest && wrap.closest('.impulse-screen');
            if (screen) return screen;
            var node = wrap;
            while (node && node !== document.body) {
                if (node.classList && node.classList.contains('impulse-el') &&
                    node.getAttribute('data-type') === 'section') {
                    var p = node.parentElement;
                    if (p && p.classList && p.classList.contains('impulse-content-wrapper')) {
                        return node;
                    }
                }
                node = node.parentElement;
            }
            return null;
        })();
        function _needsScreenGate(layers) {
            if (!_screenHost) return false;

            
            return layers.some(function(l) { return l && l.effect === 'ring_trace'; });
        }
        function _screenIsActive() {
            
            if (!_screenHost) return true;
            var body = (_screenHost.ownerDocument && _screenHost.ownerDocument.body) || document.body;
            if (!body || body.getAttribute('data-screen-mode') !== '1') return true;

            

            if (body.classList && body.classList.contains('impulse-scroll-classic')) return true;
            return _screenHost.classList.contains('impulse-screen-active');
        }

        

        
        function _readRingTraceDelay(layers, canvasW) {
            for (var i = 0; i < layers.length; i++) {
                var l = layers[i];
                if (!l || l.effect !== 'ring_trace') continue;
                var dev = canvasW < 768 ? 'mobile' : (canvasW < 1024 ? 'tablet' : 'desktop');
                if (dev !== 'desktop') {
                    var dk = l['delay_' + dev];
                    if (dk !== undefined && dk !== '' && dk !== null) return +dk || 0;
                }
                var bk = l.delay;
                if (bk !== undefined && bk !== '' && bk !== null) return +bk || 0;
                return 0;
            }
            return 0;
        }

        function animate(timestamp) {
            if (state.cancelled || state.paused) { rafId = null; return; }
            var currentCfg = state.cfg;

            

            

            
            if (_needsScreenGate(currentCfg.layers)) {
                var rtActive = _screenIsActive();
                if (!rtActive) {
                    state._waitingScreen = true;
                    state._delayUntil = 0;
                    contexts.forEach(function(ctx) {
                        if (ctx) { try { ctx.clearRect(0, 0, w, h); } catch (_) {} }
                    });
                    rafId = requestAnimationFrame(animate);
                    return;
                }

                
                if (state._waitingScreen || state._delayUntil === undefined) {
                    state._waitingScreen = false;
                    var rtDelaySec = _readRingTraceDelay(currentCfg.layers, w);
                    if (rtDelaySec > 0) {
                        state._delayUntil = (timestamp / 1000) + rtDelaySec;
                    } else {
                        state._delayUntil = 0;
                        startTime = timestamp / 1000;
                    }
                }
                if (state._delayUntil > 0) {
                    if ((timestamp / 1000) < state._delayUntil) {

                        
                        contexts.forEach(function(ctx) {
                            if (ctx) { try { ctx.clearRect(0, 0, w, h); } catch (_) {} }
                        });
                        rafId = requestAnimationFrame(animate);
                        return;
                    }

                    startTime = timestamp / 1000;
                    state._delayUntil = 0;
                }
            }
            var time = (timestamp / 1000) - startTime;
            _impMouseTick();
            contexts.forEach(function(ctx, i) {
                if (!ctx) return; 
                try {
                    ctx.clearRect(0, 0, w, h);
                    if (currentCfg.layers[i]) drawEffect(ctx, currentCfg.layers[i], w, h, time);
                } catch (_drawErr) {

                    
                }
            });
            rafId = requestAnimationFrame(animate);
        }

        state.start = function() {
            if (state.cancelled) return;
            state.paused = false;
            if (!rafId) rafId = requestAnimationFrame(animate);
        };
        state.pause = function() {
            state.paused = true;
            if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
        };

        if (!immediate && typeof IntersectionObserver !== 'undefined') {
            var target = cfg.fullscreen ? document.documentElement : wrap.parentElement || wrap;
            state._observer = new IntersectionObserver(function(entries) {
                if (state.cancelled) return;
                if (entries[0].isIntersecting) { state.start(); }
                else { state.pause(); }
            }, { rootMargin: '200px' });
            state._observer.observe(target);
        } else {
            state.start();
        }

        if (cfg.fullscreen) {
            window.addEventListener("resize", function() { if (!state.cancelled) resizeCanvases(window.innerWidth, window.innerHeight); });
        } else if (typeof ResizeObserver !== "undefined") {
            var section = wrap.parentElement;
            if (section) {
                new ResizeObserver(function() {
                    if (state.cancelled) return;
                    var nw = wrap.offsetWidth, nh = wrap.offsetHeight;
                    if (nw < 10 || nh < 10 || (nw === w && nh === h)) return;
                    resizeCanvases(nw, nh);
                }).observe(section);
            }
        }
    }

    if (immediate) {
        init();
    } else if (document.readyState === "complete") {
        init();
    } else {
        window.addEventListener("load", init);
    }
}

function updateAnimation(wrapId, newLayerConfig) {
    var state = activeAnimations[wrapId];
    if (!state || state.cancelled) return false;

    

    
    
    var _liveWrap = document.getElementById(wrapId);
    var _stateCanvas = state._canvas || null;
    if (!_liveWrap) { return false; }
    if (_stateCanvas && (!_stateCanvas.isConnected || !_liveWrap.contains(_stateCanvas))) {
        try { destroyAnimation(wrapId); } catch (_e) {}
        return false;
    }
    if (state.cfg.layers[0]) {
        
        var _oldEffect = state.cfg.layers[0].effect || '';
        var _newEffect = (newLayerConfig.effect !== undefined) ? (newLayerConfig.effect || '') : _oldEffect;

        
        var _isGL = function (e) { return e === 'lava' || e === 'grid'; };

        if (_isGL(_oldEffect) !== _isGL(_newEffect)) return false;
        if (_oldEffect !== _newEffect && (_isGL(_oldEffect) || _isGL(_newEffect))) return false;

        
        if (_newEffect === 'grid') {
            for (var gk in newLayerConfig) { state.cfg.layers[0][gk] = newLayerConfig[gk]; }
            var _gc = state._gridCanvases && state._gridCanvases[0];
            if (_gc && window.Impulse3D && window.Impulse3D.update) {
                try { window.Impulse3D.update(_gc, sectionGridOpts(state.cfg.layers[0])); } catch (_) {}
            }
            return true;
        }

        for (var key in newLayerConfig) {
            state.cfg.layers[0][key] = newLayerConfig[key];
        }

        
        var inst = state._lavaInsts && state._lavaInsts[0];
        if (inst && state.cfg.layers[0].effect === 'lava') {
            try {
                if (typeof inst.updateColors === 'function') {
                    inst.updateColors([
                        state.cfg.layers[0].color   || '#16181c',
                        state.cfg.layers[0].color_2 || '#2d3580',
                        state.cfg.layers[0].color_3 || '#5040b0',
                        state.cfg.layers[0].color_4 || '#7038ff'
                    ]);
                }
                if (typeof inst.updateSpeed === 'function') {
                    inst.updateSpeed(Math.max(0.05, (parseFloat(state.cfg.layers[0].speed) || 1) * 0.1));
                }
                if (typeof inst.setMouseSensitivity === 'function') {
                    var _msNew = (state.cfg.layers[0].mouse_sens !== undefined) ? parseFloat(state.cfg.layers[0].mouse_sens) : 5;
                    inst.setMouseSensitivity(Math.max(0, _msNew) * 0.1);
                }
                if (typeof inst.setShadowScale === 'function') {
                    var _shNew = state.cfg.layers[0].shadow_scale;
                    inst.setShadowScale((_shNew !== undefined && _shNew !== '')
                        ? Math.max(0, Math.min(1, parseFloat(_shNew) / 100))
                        : 1);
                }
                if (typeof inst.setEdgeSharpness === 'function') {
                    var _edgeNew = state.cfg.layers[0].edge_sharpness;
                    var _edgeN = (_edgeNew !== undefined && _edgeNew !== '')
                        ? parseFloat(_edgeNew) : 50;
                    inst.setEdgeSharpness(1 + (Math.max(0, Math.min(100, _edgeN)) / 100) * 6);
                }
            } catch (_) {}
        }
        return true;
    }
    return false;
}

function destroyAnimation(wrapId) {
    if (activeAnimations[wrapId]) {
        activeAnimations[wrapId].cancelled = true;

        
        if (activeAnimations[wrapId]._lavaInsts) {
            activeAnimations[wrapId]._lavaInsts.forEach(function(inst) {
                if (inst && typeof inst.destroy === 'function') {
                    try { inst.destroy(); } catch (_) {}
                }
            });
        }
        if (activeAnimations[wrapId]._gridCanvases && window.Impulse3D) {
            activeAnimations[wrapId]._gridCanvases.forEach(function(cv) {
                try { window.Impulse3D.destroy(cv); } catch (_) {}
            });
        }
        if (activeAnimations[wrapId]._observer) {
            activeAnimations[wrapId]._observer.disconnect();
        }
        delete activeAnimations[wrapId];
    }
}

window.ImpulseAnimations = { init: initAnimation, update: updateAnimation, destroy: destroyAnimation, active: activeAnimations };
})();
