
(function(){
    var st = null;
    function clamp(v){ return Math.max(0, Math.min(100, v)); }
    function fmt(p){ return p[0].toFixed(2) + ',' + p[1].toFixed(2); }

    

    
    
    var AUTO_T_FACTOR = 1/20;
    function parsePt(s, def){
        if (typeof s !== 'string' || s.indexOf(',') < 0) return def.slice();
        var a = s.split(',').map(parseFloat);
        return [clamp(isNaN(a[0])?def[0]:a[0]), clamp(isNaN(a[1])?def[1]:a[1])];
    }
    function third(a, b){ return [a[0] + (b[0]-a[0])/3, a[1] + (b[1]-a[1])/3]; }
    function twoThirds(a, b){ return [a[0] + 2*(b[0]-a[0])/3, a[1] + 2*(b[1]-a[1])/3]; }

    
    
    function parseNodesJson(s){
        if (!s || typeof s !== 'string') return [];
        try {
            var arr = JSON.parse(s);
            if (!Array.isArray(arr)) return [];
            var out = [];
            for (var i = 0; i < arr.length; i++) {
                var n = arr[i];
                if (!n || typeof n.x !== 'number' || typeof n.y !== 'number') continue;
                if (!isFinite(n.x) || !isFinite(n.y)) continue;
                var node = [clamp(n.x), clamp(n.y)];
                
                if (typeof n.tx === 'number' && typeof n.ty === 'number'
                    && isFinite(n.tx) && isFinite(n.ty)) {
                    node.tangent = [n.tx, n.ty];
                }
                out.push(node);
            }
            return out;
        } catch (e) { return []; }
    }

    

    

    function buildAnchorSequence(){
        var a = [];
        a.push({ p: st.tl, t: st.tlT || null, src: 'tl' });
        var i, nd;
        if (st.tNodes) for (i = 0; i < st.tNodes.length; i++) {
            nd = st.tNodes[i];
            a.push({ p: nd, t: nd.tangent || null, src: 'tNode-' + i });
        }
        a.push({ p: st.tr, t: st.trT || null, src: 'tr' });
        if (st.rNodes) for (i = 0; i < st.rNodes.length; i++) {
            nd = st.rNodes[i];
            a.push({ p: nd, t: nd.tangent || null, src: 'rNode-' + i });
        }
        a.push({ p: st.br, t: st.brT || null, src: 'br' });
        if (st.bNodes) for (i = 0; i < st.bNodes.length; i++) {
            nd = st.bNodes[i];
            a.push({ p: nd, t: nd.tangent || null, src: 'bNode-' + i });
        }
        a.push({ p: st.bl, t: st.blT || null, src: 'bl' });
        if (st.lNodes) for (i = 0; i < st.lNodes.length; i++) {
            nd = st.lNodes[i];
            a.push({ p: nd, t: nd.tangent || null, src: 'lNode-' + i });
        }
        return a;
    }
    function closedLoopPath(anchors, fmt){
        var n = anchors.length;
        if (n < 2) return '';
        
        var eff = new Array(n);
        for (var i = 0; i < n; i++) {
            if (anchors[i].t) {
                eff[i] = anchors[i].t;
            } else {
                var prev = anchors[(i - 1 + n) % n].p;
                var next = anchors[(i + 1) % n].p;
                eff[i] = [(next[0] - prev[0]) * AUTO_T_FACTOR, (next[1] - prev[1]) * AUTO_T_FACTOR];
            }
        }
        var out = 'M ' + fmt(anchors[0].p);
        for (var j = 0; j < n; j++) {
            var p0 = anchors[j].p;
            var p1 = anchors[(j + 1) % n].p;
            var t0 = eff[j];
            var t1 = eff[(j + 1) % n];
            var c1 = [p0[0] + t0[0], p0[1] + t0[1]];
            var c2 = [p1[0] - t1[0], p1[1] - t1[1]];
            out += ' C ' + fmt(c1) + ' ' + fmt(c2) + ' ' + fmt(p1);
        }
        return out + ' Z';
    }

    function parseTangent(s){
        if (!s || typeof s !== 'string' || s.indexOf(',') < 0) return null;
        var a = s.split(',').map(parseFloat);
        if (a.length < 2 || !isFinite(a[0]) || !isFinite(a[1])) return null;
        return [a[0], a[1]];
    }

    
    function autoTangentFor(side){
        var seq = buildAnchorSequence();
        for (var i = 0; i < seq.length; i++) {
            if (seq[i].src === side) {
                var prev = seq[(i - 1 + seq.length) % seq.length].p;
                var next = seq[(i + 1) % seq.length].p;
                return [(next[0] - prev[0]) * AUTO_T_FACTOR, (next[1] - prev[1]) * AUTO_T_FACTOR];
            }
        }
        return [0, 0];
    }
    
    function quadToCubic(oldStr, p0, p2, defC1, defC2){
        if (!oldStr || typeof oldStr !== 'string' || oldStr.indexOf(',') < 0) return [defC1, defC2];
        var q = oldStr.split(',').map(parseFloat);
        if (isNaN(q[0]) || isNaN(q[1])) return [defC1, defC2];
        return [
            [p0[0] + 2/3*(q[0]-p0[0]), p0[1] + 2/3*(q[1]-p0[1])],
            [p2[0] + 2/3*(q[0]-p2[0]), p2[1] + 2/3*(q[1]-p2[1])]
        ];
    }

    function enterCrop(opts){

        

        

        

        
        
        if (st && st.id === opts.id && st.wrap && !st.wrap.isConnected) {
            exitCrop();
            
        } else if (st && st.id === opts.id) {
            var _oT2 = parseInt(opts.oldT)||0, _oR2 = parseInt(opts.oldR)||0;
            var _oB2 = parseInt(opts.oldB)||0, _oL2 = parseInt(opts.oldL)||0;
            var _dTL2 = [_oL2, _oT2], _dTR2 = [100-_oR2, _oT2];
            var _dBR2 = [100-_oR2, 100-_oB2], _dBL2 = [_oL2, 100-_oB2];
            st.tl = parsePt(opts.tl, _dTL2);
            st.tr = parsePt(opts.tr, _dTR2);
            st.br = parsePt(opts.br, _dBR2);
            st.bl = parsePt(opts.bl, _dBL2);
            var _ts = quadToCubic(opts.oldTopC,    st.tl, st.tr, third(st.tl, st.tr), twoThirds(st.tl, st.tr));
            var _rs = quadToCubic(opts.oldRightC,  st.tr, st.br, third(st.tr, st.br), twoThirds(st.tr, st.br));
            var _bs = quadToCubic(opts.oldBottomC, st.br, st.bl, third(st.br, st.bl), twoThirds(st.br, st.bl));
            var _ls = quadToCubic(opts.oldLeftC,   st.bl, st.tl, third(st.bl, st.tl), twoThirds(st.bl, st.tl));
            st.tC1 = parsePt(opts.tC1, _ts[0]); st.tC2 = parsePt(opts.tC2, _ts[1]);
            st.rC1 = parsePt(opts.rC1, _rs[0]); st.rC2 = parsePt(opts.rC2, _rs[1]);
            st.bC1 = parsePt(opts.bC1, _bs[0]); st.bC2 = parsePt(opts.bC2, _bs[1]);
            st.lC1 = parsePt(opts.lC1, _ls[0]); st.lC2 = parsePt(opts.lC2, _ls[1]);
            st.tNodes = parseNodesJson(opts.topNodes);
            st.rNodes = parseNodesJson(opts.rightNodes);
            st.bNodes = parseNodesJson(opts.bottomNodes);
            st.lNodes = parseNodesJson(opts.leftNodes);
            st.tlT = parseTangent(opts.tlTangent);
            st.trT = parseTangent(opts.trTangent);
            st.brT = parseTangent(opts.brTangent);
            st.blT = parseTangent(opts.blTangent);
            st.rTL = Math.max(0, parseFloat(opts.rTL) || 0);
            st.rTR = Math.max(0, parseFloat(opts.rTR) || 0);
            st.rBR = Math.max(0, parseFloat(opts.rBR) || 0);
            st.rBL = Math.max(0, parseFloat(opts.rBL) || 0);
            layoutCrop();
            return;
        }
        if (st) exitCrop();
        var el = document.querySelector('.impulse-el[data-id="' + opts.id + '"]');
        if (!el) return;

        

        
        
        var img = el.querySelector('img.impulse-img')
               || el.querySelector('video.impulse-video-media')
               || el.querySelector('iframe');
        var wrap = (img && (img.closest('.impulse-image-wrapper, .impulse-video') || img.parentElement))
                || el.querySelector('.impulse-image-wrapper, .impulse-video');
        if (!wrap) return;

        var oT = parseInt(opts.oldT)||0, oR = parseInt(opts.oldR)||0;
        var oB = parseInt(opts.oldB)||0, oL = parseInt(opts.oldL)||0;
        var defTL = [oL, oT], defTR = [100-oR, oT];
        var defBR = [100-oR, 100-oB], defBL = [oL, 100-oB];

        st = {
            id: opts.id, img: img, wrap: wrap,
            tl: parsePt(opts.tl, defTL),
            tr: parsePt(opts.tr, defTR),
            br: parsePt(opts.br, defBR),
            bl: parsePt(opts.bl, defBL),
            dragRole: null
        };

        
        var topSeed    = quadToCubic(opts.oldTopC,    st.tl, st.tr, third(st.tl, st.tr), twoThirds(st.tl, st.tr));
        var rightSeed  = quadToCubic(opts.oldRightC,  st.tr, st.br, third(st.tr, st.br), twoThirds(st.tr, st.br));
        var bottomSeed = quadToCubic(opts.oldBottomC, st.br, st.bl, third(st.br, st.bl), twoThirds(st.br, st.bl));
        var leftSeed   = quadToCubic(opts.oldLeftC,   st.bl, st.tl, third(st.bl, st.tl), twoThirds(st.bl, st.tl));
        st.tC1 = parsePt(opts.tC1, topSeed[0]);    st.tC2 = parsePt(opts.tC2, topSeed[1]);
        st.rC1 = parsePt(opts.rC1, rightSeed[0]);  st.rC2 = parsePt(opts.rC2, rightSeed[1]);
        st.bC1 = parsePt(opts.bC1, bottomSeed[0]); st.bC2 = parsePt(opts.bC2, bottomSeed[1]);
        st.lC1 = parsePt(opts.lC1, leftSeed[0]);   st.lC2 = parsePt(opts.lC2, leftSeed[1]);
        
        st.tNodes = parseNodesJson(opts.topNodes);
        st.rNodes = parseNodesJson(opts.rightNodes);
        st.bNodes = parseNodesJson(opts.bottomNodes);
        st.lNodes = parseNodesJson(opts.leftNodes);

        
        st.tlT = parseTangent(opts.tlTangent);
        st.trT = parseTangent(opts.trTangent);
        st.brT = parseTangent(opts.brTangent);
        st.blT = parseTangent(opts.blTangent);

        st.rTL = Math.max(0, parseFloat(opts.rTL) || 0);
        st.rTR = Math.max(0, parseFloat(opts.rTR) || 0);
        st.rBR = Math.max(0, parseFloat(opts.rBR) || 0);
        st.rBL = Math.max(0, parseFloat(opts.rBL) || 0);

        var _wrapRect = wrap.getBoundingClientRect();
        st.rectWPx = _wrapRect.width;
        st.rectHPx = _wrapRect.height;

        if (getComputedStyle(wrap).position === 'static') wrap.style.position = 'relative';

        

        
        var _r1     = wrap.getBoundingClientRect();
        var _iw     = window.innerWidth, _ih = window.innerHeight;
        var _margin = 30; 
        var _clips  = wrap.classList.contains('impulse-image-pinned')
                   && (_r1.left   < _margin
                    || _r1.top    < _margin
                    || _r1.right  > _iw - _margin
                    || _r1.bottom > _ih - _margin);
        st._savedStyle = wrap.getAttribute('style') || '';
        st._lifted     = _clips;
        if (_clips) {
            wrap.classList.add('impulse-image-editing');
            wrap.style.setProperty('position',   'fixed',                'important');
            wrap.style.setProperty('top',        '50%',                  'important');
            wrap.style.setProperty('left',       '50%',                  'important');
            wrap.style.setProperty('right',      'auto',                 'important');
            wrap.style.setProperty('bottom',     'auto',                 'important');
            wrap.style.setProperty('transform',  'translate(-50%, -50%)','important');
            wrap.style.setProperty('width',      'min(70vw, 900px)',     'important');
            wrap.style.setProperty('height',     'auto',                 'important');
            wrap.style.setProperty('max-height', '80vh',                 'important');
            wrap.style.setProperty('z-index',    '100001',               'important');
            wrap.style.setProperty('transition', 'all 220ms ease-in-out','important');
        }
        
        var _r2 = wrap.getBoundingClientRect();
        st.rectWPx = _r2.width;
        st.rectHPx = _r2.height;

        var clipId = 'impulse-crop-clip-live-' + opts.id;
        var ns = 'http://www.w3.org/2000/svg';
        var svgEl = document.createElementNS(ns, 'svg');
        svgEl.setAttribute('width','0'); svgEl.setAttribute('height','0');
        svgEl.setAttribute('aria-hidden','true');
        svgEl.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden';
        var clipEl = document.createElementNS(ns, 'clipPath');
        clipEl.setAttribute('id', clipId);
        clipEl.setAttribute('clipPathUnits','objectBoundingBox');
        var pathEl = document.createElementNS(ns, 'path');
        clipEl.appendChild(pathEl);
        svgEl.appendChild(clipEl);
        wrap.appendChild(svgEl);
        st.svgEl = svgEl; st.pathEl = pathEl; st.clipId = clipId;

        

        
        wrap.style.webkitClipPath = 'none'; wrap.style.clipPath = 'none';

        
        if (img) {
            img.style.webkitClipPath = 'url(#' + clipId + ')';
            img.style.clipPath = 'url(#' + clipId + ')';
        }

        var ov = document.createElement('div');
        ov.className = 'impulse-crop-overlay';
        ov.style.cssText = 'position:absolute;inset:0;z-index:100000;user-select:none;pointer-events:auto';

        
        
        var cornerStyle = 'position:absolute;width:10px;height:10px;border-radius:50%;background:#fff;border:1px solid #1d2327;transform:translate(-50%,-50%);z-index:4;cursor:move';
        var ctrlStyle = 'position:absolute;width:8px;height:8px;background:#fff;border:1px solid #1d2327;transform:translate(-50%,-50%) rotate(45deg);z-index:3;cursor:grab';

        var radStyle = 'position:absolute;width:14px;height:14px;border-radius:50%;background:transparent;border:1px solid #1d2327;transform:translate(-50%,-50%);z-index:5;cursor:nwse-resize;pointer-events:auto';
        ov.innerHTML = [
            '<svg class="ic-crop-svg" viewBox="0 0 100 100" preserveAspectRatio="none" style="position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible">',
              
              '<path class="ic-crop-line" d="" fill="none" stroke="#fff" stroke-width="0.5" vector-effect="non-scaling-stroke" style="filter:drop-shadow(0 0 1px rgba(0,0,0,0.65))"/>',
            '</svg>',
            '<div class="ic-crop-h" data-r="tl" style="' + cornerStyle + '" title="Corner"></div>',
            '<div class="ic-crop-h" data-r="tr" style="' + cornerStyle + '" title="Corner"></div>',
            '<div class="ic-crop-h" data-r="br" style="' + cornerStyle + '" title="Corner"></div>',
            '<div class="ic-crop-h" data-r="bl" style="' + cornerStyle + '" title="Corner"></div>',

            

            
            
            '<div class="ic-crop-rh" data-r="rTL" style="' + radStyle + '" title="TL radius"></div>',
            '<div class="ic-crop-rh" data-r="rTR" style="' + radStyle + '" title="TR radius"></div>',
            '<div class="ic-crop-rh" data-r="rBR" style="' + radStyle + '" title="BR radius"></div>',
            '<div class="ic-crop-rh" data-r="rBL" style="' + radStyle + '" title="BL radius"></div>'

            
        ].join('');
        wrap.appendChild(ov);
        st.overlay = ov;
        layoutCrop();
        _watchWrapForCanvas();
        _startClipGuard();
        bindCrop();
    }

    

    

    

    

    

    

    

    
    
    function _watchWrapForCanvas(){
        if (!st || !st.wrap || typeof MutationObserver === 'undefined') return;

        if (st._canvasObs) return;
        var lastSeen = st.wrap.querySelector(':scope > canvas') || null;
        st._canvasObs = new MutationObserver(function(){
            if (!st || !st.wrap) return;
            var current = st.wrap.querySelector(':scope > canvas');

            

            if (current !== lastSeen) {
                lastSeen = current;
                if (current) layoutCrop();
            }
        });
        st._canvasObs.observe(st.wrap, { childList: true });
    }

    

    

    

    

    

    
    
    function _reAnchorClip(){
        if (!st || !st.clipId) return;
        var ref = 'url(#' + st.clipId + ')';
        if (st.wrap) {
            var c = st.wrap.querySelector(':scope > canvas');
            if (c && c.style.clipPath !== ref) {
                c.style.webkitClipPath = ref; c.style.clipPath = ref;
            }
        }
        if (st.img && st.img.isConnected && st.img.style.clipPath !== ref) {
            st.img.style.webkitClipPath = ref; st.img.style.clipPath = ref;
        }
    }
    function _startClipGuard(){
        if (!st || st._clipGuardRAF || typeof requestAnimationFrame === 'undefined') return;
        var tick = function(){
            if (!st || !st.overlay || !st.overlay.isConnected) {
                if (st) st._clipGuardRAF = null;
                return;
            }
            _reAnchorClip();
            st._clipGuardRAF = requestAnimationFrame(tick);
        };
        st._clipGuardRAF = requestAnimationFrame(tick);
    }
    function _stopClipGuard(){
        if (st && st._clipGuardRAF && typeof cancelAnimationFrame !== 'undefined') {
            cancelAnimationFrame(st._clipGuardRAF);
        }
        if (st) st._clipGuardRAF = null;
    }

    function buildPathOBB(){
        var p = function(pt){ return (pt[0]/100).toFixed(4) + ',' + (pt[1]/100).toFixed(4); };
        return closedLoopPath(buildAnchorSequence(), p);
    }
    function buildPathView(){
        var p = function(pt){ return pt[0].toFixed(2) + ',' + pt[1].toFixed(2); };
        return closedLoopPath(buildAnchorSequence(), p);
    }

    function layoutCrop(){
        if (!st) return;
        st.pathEl.setAttribute('d', buildPathOBB());
        st.overlay.querySelector('.ic-crop-line').setAttribute('d', buildPathView());

        

        

        
        if (st.clipId && st.wrap) {
            var _depthCanvas = st.wrap.querySelector(':scope > canvas');
            if (_depthCanvas) {
                var _ref = 'url(#' + st.clipId + ')';
                if (_depthCanvas.style.clipPath !== _ref) {
                    _depthCanvas.style.webkitClipPath = _ref;
                    _depthCanvas.style.clipPath = _ref;
                }
            }

            

            

            
            if (st.img && st.img.isConnected) {
                var _imgRef = 'url(#' + st.clipId + ')';
                if (st.img.style.clipPath !== _imgRef) {
                    st.img.style.webkitClipPath = _imgRef;
                    st.img.style.clipPath = _imgRef;
                }
            }
        }

        var roles = ['tl','tr','br','bl'];
        roles.forEach(function(r){
            var h = st.overlay.querySelector('.ic-crop-h[data-r="' + r + '"]');
            if (h) { h.style.left = st[r][0] + '%'; h.style.top = st[r][1] + '%'; }
        });

        

        
        st.wrap.style.setProperty('border-radius',
            st.rTL + 'px ' + st.rTR + 'px ' + st.rBR + 'px ' + st.rBL + 'px',
            'important');

        

        

        st.wrap.style.setProperty('overflow', 'visible', 'important');
        var rXpct = function(px){ return st.rectWPx ? (px / st.rectWPx) * 100 : 0; };
        var rYpct = function(px){ return st.rectHPx ? (px / st.rectHPx) * 100 : 0; };
        var radPositions = {
            rTL: [st.tl[0] + rXpct(st.rTL), st.tl[1] + rYpct(st.rTL)],
            rTR: [st.tr[0] - rXpct(st.rTR), st.tr[1] + rYpct(st.rTR)],
            rBR: [st.br[0] - rXpct(st.rBR), st.br[1] - rYpct(st.rBR)],
            rBL: [st.bl[0] + rXpct(st.rBL), st.bl[1] - rYpct(st.rBL)]
        };
        Object.keys(radPositions).forEach(function(r){
            var h = st.overlay.querySelector('.ic-crop-rh[data-r="' + r + '"]');
            if (!h) return;
            h.style.left = radPositions[r][0] + '%';
            h.style.top  = radPositions[r][1] + '%';
        });

        

        
        var nodeBox = st.overlay.querySelector('.ic-crop-nodes');
        if (!nodeBox) {
            nodeBox = document.createElement('div');
            nodeBox.className = 'ic-crop-nodes';
            nodeBox.style.cssText = 'position:absolute;inset:0;pointer-events:none';
            st.overlay.appendChild(nodeBox);
        }
        nodeBox.innerHTML = '';
        
        var nodeStyle = 'position:absolute;width:7px;height:7px;background:#fff;'
                      + 'border:1px solid #1d2327;'
                      + 'transform:translate(-50%,-50%) rotate(45deg);'
                      + 'z-index:3;pointer-events:auto;cursor:grab';
        var sideKey = { t: 'tNodes', r: 'rNodes', b: 'bNodes', l: 'lNodes' };
        ['t','r','b','l'].forEach(function(side){
            var nodes = st[sideKey[side]] || [];
            nodes.forEach(function(node, i){
                var h = document.createElement('div');
                h.className = 'ic-crop-nh';
                h.setAttribute('data-side', side);
                h.setAttribute('data-i', String(i));
                h.style.cssText = nodeStyle
                                + ';left:' + node[0] + '%'
                                + ';top:'  + node[1] + '%';
                h.title = 'Drag to move · dbl-click / right-click to delete';
                nodeBox.appendChild(h);
            });
        });

        

        
        
        var tangentBox = st.overlay.querySelector('.ic-crop-tangents');
        if (!tangentBox) {
            tangentBox = document.createElement('div');
            tangentBox.className = 'ic-crop-tangents';
            tangentBox.style.cssText = 'position:absolute;inset:0;pointer-events:none';
            st.overlay.insertBefore(tangentBox, st.overlay.firstChild.nextSibling);
        }
        tangentBox.innerHTML = '';

        

        

        var tangentDotStyle = st.displayOnly
            ? 'position:absolute;width:6px;height:6px;'
              + 'border-radius:50%;background:#fff;'
              + 'box-shadow:0 0 0 1px rgba(0,0,0,0.45),0 0 0 2px rgba(255,255,255,0.55);'
              + 'transform:translate(-50%,-50%);z-index:6;'
              + 'pointer-events:auto;cursor:grab'
            : 'position:absolute;width:5px;height:5px;'
              + 'border-radius:50%;background:#fff;'
              + 'box-shadow:0 0 0 1px rgba(0,0,0,0.65);'
              + 'transform:translate(-50%,-50%);z-index:6;'
              + 'pointer-events:auto;cursor:grab';
        var anchorSeq = buildAnchorSequence();
        var nA = anchorSeq.length;
        
        var effT = new Array(nA);
        for (var i = 0; i < nA; i++) {
            if (anchorSeq[i].t) {
                effT[i] = anchorSeq[i].t;
            } else {
                var prevP = anchorSeq[(i - 1 + nA) % nA].p;
                var nextP = anchorSeq[(i + 1) % nA].p;
                effT[i] = [(nextP[0] - prevP[0]) * AUTO_T_FACTOR, (nextP[1] - prevP[1]) * AUTO_T_FACTOR];
            }
        }
        
        var lineSvg = '<svg width="100%" height="100%" viewBox="0 0 100 100" '
                    + 'preserveAspectRatio="none" '
                    + 'style="position:absolute;inset:0;overflow:visible;'
                    + 'pointer-events:none;z-index:1">';
        for (var k = 0; k < nA; k++) {
            var pp = anchorSeq[k].p;
            var tt = effT[k];
            lineSvg += '<line x1="' + (pp[0] - tt[0]) + '" y1="' + (pp[1] - tt[1]) + '" '
                     + 'x2="' + (pp[0] + tt[0]) + '" y2="' + (pp[1] + tt[1]) + '" '
                     + 'stroke="#1d2327" stroke-width="0.5" '
                     + 'vector-effect="non-scaling-stroke" stroke-dasharray="3 2"/>';
        }
        lineSvg += '</svg>';
        var svgWrap = document.createElement('div');
        svgWrap.style.cssText = 'position:absolute;inset:0;pointer-events:none';
        svgWrap.innerHTML = lineSvg;
        tangentBox.appendChild(svgWrap);
        
        for (var j = 0; j < nA; j++) {
            var entry = anchorSeq[j];
            var p = entry.p;
            var t = effT[j];
            [+1, -1].forEach(function(sign){
                var dot = document.createElement('div');
                dot.className = 'ic-crop-th';
                dot.setAttribute('data-src',  entry.src);
                dot.setAttribute('data-sign', String(sign));
                dot.style.cssText = tangentDotStyle
                                  + ';left:' + (p[0] + sign * t[0]) + '%'
                                  + ';top:'  + (p[1] + sign * t[1]) + '%';
                dot.title = 'Drag to widen or rotate the curve through this anchor';
                tangentBox.appendChild(dot);
            });
        }
    }

    var NODE_SIDE_KEY = { t: 'tNodes', r: 'rNodes', b: 'bNodes', l: 'lNodes' };
    function deleteNode(side, idx){
        var key = NODE_SIDE_KEY[side];
        if (!key) return;
        var arr = st[key];
        if (!Array.isArray(arr) || idx < 0 || idx >= arr.length) return;
        arr.splice(idx, 1);
        layoutCrop();
        commitNow();
    }

    
    
    function insertNodeAt(x, y){
        function distToSeg(px, py, ax, ay, bx, by){
            var dx = bx - ax, dy = by - ay;
            var len2 = dx*dx + dy*dy;
            if (len2 < 1e-6) {
                var ex = px - ax, ey = py - ay;
                return Math.sqrt(ex*ex + ey*ey);
            }
            var t = ((px-ax)*dx + (py-ay)*dy) / len2;
            if (t < 0) t = 0; else if (t > 1) t = 1;
            var cx = ax + t*dx, cy = ay + t*dy;
            var ex2 = px - cx, ey2 = py - cy;
            return Math.sqrt(ex2*ex2 + ey2*ey2);
        }
        var sides = [
            { side: 't', a: st.tl, b: st.tr },
            { side: 'r', a: st.tr, b: st.br },
            { side: 'b', a: st.br, b: st.bl },
            { side: 'l', a: st.bl, b: st.tl }
        ];
        var best = null, bestDist = Infinity;
        for (var i = 0; i < sides.length; i++) {
            var d = distToSeg(x, y, sides[i].a[0], sides[i].a[1], sides[i].b[0], sides[i].b[1]);
            if (d < bestDist) { bestDist = d; best = sides[i]; }
        }
        if (!best) return;
        var node = [clamp(x), clamp(y)];

        function tAlong(n){
            var dx = best.b[0] - best.a[0], dy = best.b[1] - best.a[1];
            var len2 = dx*dx + dy*dy;
            if (len2 < 1e-6) return 0;
            return ((n[0]-best.a[0])*dx + (n[1]-best.a[1])*dy) / len2;
        }
        var key = NODE_SIDE_KEY[best.side];
        var arr = st[key] || [];
        var newT = tAlong(node);
        var insertIdx = 0;
        for (var k = 0; k < arr.length; k++) {
            if (tAlong(arr[k]) <= newT) insertIdx = k + 1;
        }
        arr.splice(insertIdx, 0, node);
        st[key] = arr;
        layoutCrop();
        commitNow();
    }

    function commitNow(){
        if (!st) return;

        
        if (st.displayOnly) return;
        function nodesJson(arr){
            if (!Array.isArray(arr) || !arr.length) return '';
            return JSON.stringify(arr.map(function(n){
                var o = {x:n[0], y:n[1]};
                if (n.tangent && isFinite(n.tangent[0]) && isFinite(n.tangent[1])) {
                    o.tx = n.tangent[0]; o.ty = n.tangent[1];
                }
                return o;
            }));
        }
        function tangentStr(t){ return t ? fmt(t) : ''; }
        window.parent.postMessage({
            type: 'impulse-image-crop-commit',
            id: st.id,
            tl: fmt(st.tl), tr: fmt(st.tr), br: fmt(st.br), bl: fmt(st.bl),

            
            tC1: fmt(st.tC1), tC2: fmt(st.tC2),
            rC1: fmt(st.rC1), rC2: fmt(st.rC2),
            bC1: fmt(st.bC1), bC2: fmt(st.bC2),
            lC1: fmt(st.lC1), lC2: fmt(st.lC2),
            rTL: st.rTL, rTR: st.rTR, rBR: st.rBR, rBL: st.rBL,
            
            topNodes:    nodesJson(st.tNodes),
            rightNodes:  nodesJson(st.rNodes),
            bottomNodes: nodesJson(st.bNodes),
            leftNodes:   nodesJson(st.lNodes),
            
            tlTangent: tangentStr(st.tlT),
            trTangent: tangentStr(st.trT),
            brTangent: tangentStr(st.brT),
            blTangent: tangentStr(st.blT)
        }, '*');
    }

    function resetCrop(){
        st.tl=[0,0]; st.tr=[100,0]; st.br=[100,100]; st.bl=[0,100];
        st.tC1 = third(st.tl, st.tr); st.tC2 = twoThirds(st.tl, st.tr);
        st.rC1 = third(st.tr, st.br); st.rC2 = twoThirds(st.tr, st.br);
        st.bC1 = third(st.br, st.bl); st.bC2 = twoThirds(st.br, st.bl);
        st.lC1 = third(st.bl, st.tl); st.lC2 = twoThirds(st.bl, st.tl);
        st.rTL = 0; st.rTR = 0; st.rBR = 0; st.rBL = 0;
        st.tNodes = []; st.rNodes = []; st.bNodes = []; st.lNodes = [];
        st.tlT = null; st.trT = null; st.brT = null; st.blT = null;
        layoutCrop();
        commitNow();
    }

    function bindCrop(){
        if (!st) return;
        var ov = st.overlay;
        var onDown = function(e, role){
            e.preventDefault(); e.stopPropagation();
            var r = st.wrap.getBoundingClientRect();
            st.dragRole = role;
            st.rectW = r.width; st.rectH = r.height; st.rectL = r.left; st.rectT = r.top;
            document.addEventListener('mousemove', onMove);
            document.addEventListener('mouseup', onUp);
        };
        var onMove = function(e){
            if (!st || !st.dragRole) return;
            var role = st.dragRole;

            if (role === 'rTL' || role === 'rTR' || role === 'rBR' || role === 'rBL') {
                var cornerKey = { rTL:'tl', rTR:'tr', rBR:'br', rBL:'bl' }[role];
                var cornerPxX = st[cornerKey][0] / 100 * st.rectW + st.rectL;
                var cornerPxY = st[cornerKey][1] / 100 * st.rectH + st.rectT;
                var dxPx = e.clientX - cornerPxX;
                var dyPx = e.clientY - cornerPxY;
                
                var dirX = (role === 'rTL' || role === 'rBL') ? 1 : -1;
                var dirY = (role === 'rTL' || role === 'rTR') ? 1 : -1;
                var inX = dxPx * dirX, inY = dyPx * dirY;
                var radPx = Math.min(Math.max(0, inX), Math.max(0, inY));
                var maxR = Math.min(st.rectW, st.rectH) / 2;
                radPx = Math.max(0, Math.min(maxR, radPx));
                st[role] = radPx;
                layoutCrop();
                return;
            }

            
            
            if (role.indexOf('tangent|') === 0) {
                var tParts = role.split('|');
                var tSrc  = tParts[1];
                var tSign = parseInt(tParts[2], 10) || 1;
                var anchor = null;
                if (tSrc === 'tl' || tSrc === 'tr' || tSrc === 'br' || tSrc === 'bl') {
                    anchor = st[tSrc];
                } else {
                    var nm = tSrc.match(/^([trbl])Node-(\d+)$/);
                    if (nm) {
                        var nList = st[nm[1] + 'Nodes'];
                        if (nList) anchor = nList[parseInt(nm[2], 10)];
                    }
                }
                if (anchor) {
                    var px = ((e.clientX - st.rectL) / Math.max(1, st.rectW)) * 100;
                    var py = ((e.clientY - st.rectT) / Math.max(1, st.rectH)) * 100;
                    var tVec = [(px - anchor[0]) * tSign, (py - anchor[1]) * tSign];
                    if (tSrc === 'tl' || tSrc === 'tr' || tSrc === 'br' || tSrc === 'bl') {
                        st[tSrc + 'T'] = tVec;
                    } else {
                        anchor.tangent = tVec;
                    }
                    layoutCrop();
                }
                return;
            }

            if (role.indexOf('node:') === 0) {
                var parts = role.split(':');
                var nside = parts[1];
                var nidx  = parseInt(parts[2], 10);
                var nkey  = NODE_SIDE_KEY[nside];
                if (nkey && st[nkey] && st[nkey][nidx]) {
                    var nx = ((e.clientX - st.rectL) / Math.max(1, st.rectW)) * 100;
                    var ny = ((e.clientY - st.rectT) / Math.max(1, st.rectH)) * 100;
                    st[nkey][nidx] = [clamp(nx), clamp(ny)];
                    layoutCrop();
                }
                return;
            }
            
            var x = ((e.clientX - st.rectL) / Math.max(1, st.rectW)) * 100;
            var y = ((e.clientY - st.rectT) / Math.max(1, st.rectH)) * 100;
            st[role] = [clamp(x), clamp(y)];
            layoutCrop();
        };
        var onUp = function(){
            if (!st) return;
            st.dragRole = null;
            document.removeEventListener('mousemove', onMove);
            document.removeEventListener('mouseup', onUp);
            commitNow();
        };
        ov.querySelectorAll('.ic-crop-h').forEach(function(h){
            h.addEventListener('mousedown', function(e){ onDown(e, h.dataset.r); });
        });
        ov.querySelectorAll('.ic-crop-rh').forEach(function(h){
            h.addEventListener('mousedown', function(e){ onDown(e, h.dataset.r); });
        });

        ov.addEventListener('mousedown', function(e){
            var h = e.target && e.target.closest && e.target.closest('.ic-crop-nh');
            if (!h) return;
            onDown(e, 'node:' + h.dataset.side + ':' + h.dataset.i);
        });

        
        ov.addEventListener('mousedown', function(e){
            var h = e.target && e.target.closest && e.target.closest('.ic-crop-th');
            if (!h) return;
            onDown(e, 'tangent|' + h.dataset.src + '|' + h.dataset.sign);
        });

        

        
        if (!st.displayOnly) {
            ov.addEventListener('dblclick', function(e){

                var th = e.target && e.target.closest && e.target.closest('.ic-crop-th');
                if (th) {
                    e.preventDefault(); e.stopPropagation();
                    var src = th.dataset.src;
                    if (src === 'tl' || src === 'tr' || src === 'br' || src === 'bl') {
                        st[src + 'T'] = null;
                    } else {
                        var nm = src.match(/^([trbl])Node-(\d+)$/);
                        if (nm) {
                            var nList = st[nm[1] + 'Nodes'];
                            if (nList && nList[parseInt(nm[2], 10)]) {
                                delete nList[parseInt(nm[2], 10)].tangent;
                            }
                        }
                    }
                    layoutCrop();
                    commitNow();
                    return;
                }
                
                var h = e.target && e.target.closest && e.target.closest('.ic-crop-nh');
                if (h) {
                    e.preventDefault(); e.stopPropagation();
                    deleteNode(h.dataset.side, parseInt(h.dataset.i, 10));
                    return;
                }

                if (e.target && e.target.closest && (
                    e.target.closest('.ic-crop-h') || e.target.closest('.ic-crop-rh')
                )) return;
                
                e.preventDefault(); e.stopPropagation();
                var r = st.wrap.getBoundingClientRect();
                var x = ((e.clientX - r.left) / Math.max(1, r.width))  * 100;
                var y = ((e.clientY - r.top)  / Math.max(1, r.height)) * 100;
                insertNodeAt(x, y);
            });
            ov.addEventListener('contextmenu', function(e){
                var h = e.target && e.target.closest && e.target.closest('.ic-crop-nh');
                if (!h) return;
                e.preventDefault();
                deleteNode(h.dataset.side, parseInt(h.dataset.i, 10));
            });

            st._onKey = function(e){ if (e.key === 'Escape') exitCrop(); };
            document.addEventListener('keydown', st._onKey);
        }
    }

    function exitCrop(){
        if (!st) return;
        if (st.overlay && st.overlay.parentNode) st.overlay.parentNode.removeChild(st.overlay);
        if (st.svgEl && st.svgEl.parentNode) st.svgEl.parentNode.removeChild(st.svgEl);
        
        if (st.wrap) {
            st.wrap.classList.remove('impulse-image-editing');
            if (typeof st._savedStyle === 'string') {
                if (st._savedStyle) st.wrap.setAttribute('style', st._savedStyle);
                else st.wrap.removeAttribute('style');
            }
        }

        if (st.img)  { st.img.style.webkitClipPath  = ''; st.img.style.clipPath  = ''; }

        
        if (st.wrap) {
            var _dc = st.wrap.querySelector(':scope > canvas');
            if (_dc) { _dc.style.webkitClipPath = ''; _dc.style.clipPath = ''; }
        }
        if (st._onKey) document.removeEventListener('keydown', st._onKey);
        if (st._canvasObs) { try { st._canvasObs.disconnect(); } catch(_e){} st._canvasObs = null; }
        _stopClipGuard();
        st = null;
        window.parent.postMessage({ type: 'impulse-image-crop-exit' }, '*');
    }

    window.addEventListener('message', function(e){
        if (!e || !e.data) return;
        if (e.data.type === 'impulse-image-crop-enter') enterCrop(e.data);
        else if (e.data.type === 'impulse-image-crop-exit-cmd') exitCrop();
        else if (e.data.type === 'impulse-image-crop-reset') { if (st) resetCrop(); }
    });

    

    

    

    

    

    

    
    
    window.ImpulseCropOverlay = {
        mount: function(wrap, opts){
            if (!wrap || !opts || !opts.id) return null;
            
            if (st) exitCrop();
            var img = wrap.querySelector('img.impulse-img')
                   || wrap.querySelector('video.impulse-video-media')
                   || wrap.querySelector('iframe')
                   || wrap.querySelector('img');
            
            var defTL = [0,0], defTR = [100,0], defBR = [100,100], defBL = [0,100];
            st = {
                id: opts.id, img: img || null, wrap: wrap,
                tl: parsePt(opts.tl, defTL),
                tr: parsePt(opts.tr, defTR),
                br: parsePt(opts.br, defBR),
                bl: parsePt(opts.bl, defBL),
                tNodes: parseNodesJson(opts.topNodes),
                rNodes: parseNodesJson(opts.rightNodes),
                bNodes: parseNodesJson(opts.bottomNodes),
                lNodes: parseNodesJson(opts.leftNodes),
                tlT: parseTangent(opts.tlTangent),
                trT: parseTangent(opts.trTangent),
                brT: parseTangent(opts.brTangent),
                blT: parseTangent(opts.blTangent),
                rTL: Math.max(0, parseFloat(opts.rTL) || 0),
                rTR: Math.max(0, parseFloat(opts.rTR) || 0),
                rBR: Math.max(0, parseFloat(opts.rBR) || 0),
                rBL: Math.max(0, parseFloat(opts.rBL) || 0),
                dragRole: null,
                displayOnly: true,
                _savedStyle: wrap.getAttribute('style') || ''
            };
            if (getComputedStyle(wrap).position === 'static') wrap.style.position = 'relative';
            var _wrapRect = wrap.getBoundingClientRect();
            st.rectWPx = _wrapRect.width;
            st.rectHPx = _wrapRect.height;
            
            var clipId = 'impulse-crop-clip-live-' + opts.id;
            var ns = 'http://www.w3.org/2000/svg';
            var svgEl = document.createElementNS(ns, 'svg');
            svgEl.setAttribute('width','0'); svgEl.setAttribute('height','0');
            svgEl.setAttribute('aria-hidden','true');
            svgEl.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden';
            var clipEl = document.createElementNS(ns, 'clipPath');
            clipEl.setAttribute('id', clipId);
            clipEl.setAttribute('clipPathUnits','objectBoundingBox');
            var pathEl = document.createElementNS(ns, 'path');
            clipEl.appendChild(pathEl);
            svgEl.appendChild(clipEl);
            wrap.appendChild(svgEl);
            st.svgEl = svgEl; st.pathEl = pathEl; st.clipId = clipId;
            wrap.style.webkitClipPath = 'none'; wrap.style.clipPath = 'none';
            if (st.img) {
                st.img.style.webkitClipPath = 'url(#' + clipId + ')';
                st.img.style.clipPath = 'url(#' + clipId + ')';
            }

            

            

            var ov = document.createElement('div');
            ov.className = 'impulse-crop-overlay';
            ov.style.cssText = 'position:absolute;inset:0;z-index:10;user-select:none;'
                             + 'pointer-events:' + (opts.interactive ? 'auto' : 'none');
            var cornerStyle = 'position:absolute;width:10px;height:10px;border-radius:50%;background:#fff;border:1px solid #1d2327;transform:translate(-50%,-50%);z-index:4'
                            + (opts.interactive ? ';cursor:move' : '');
            
            var radStyle = 'position:absolute;width:14px;height:14px;border-radius:50%;background:transparent;border:1px solid #1d2327;transform:translate(-50%,-50%);z-index:5'
                            + (opts.interactive ? ';cursor:nwse-resize;pointer-events:auto' : '')
                            + ';display:none';
            st.interactive = !!opts.interactive;
            ov.innerHTML = [
                '<svg class="ic-crop-svg" viewBox="0 0 100 100" preserveAspectRatio="none" style="position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible">',
                  '<path class="ic-crop-line" d="" fill="none" stroke="#fff" stroke-width="0.5" vector-effect="non-scaling-stroke" style="filter:drop-shadow(0 0 1px rgba(0,0,0,0.65))"/>',
                '</svg>',
                '<div class="ic-crop-h" data-r="tl" style="' + cornerStyle + '"></div>',
                '<div class="ic-crop-h" data-r="tr" style="' + cornerStyle + '"></div>',
                '<div class="ic-crop-h" data-r="br" style="' + cornerStyle + '"></div>',
                '<div class="ic-crop-h" data-r="bl" style="' + cornerStyle + '"></div>',
                '<div class="ic-crop-rh" data-r="rTL" style="' + radStyle + '"></div>',
                '<div class="ic-crop-rh" data-r="rTR" style="' + radStyle + '"></div>',
                '<div class="ic-crop-rh" data-r="rBR" style="' + radStyle + '"></div>',
                '<div class="ic-crop-rh" data-r="rBL" style="' + radStyle + '"></div>'
            ].join('');
            wrap.appendChild(ov);
            st.overlay = ov;
            layoutCrop();
            _watchWrapForCanvas();

            
            
            if (opts.interactive) bindCrop();
            return { clipId: clipId };
        },
        setState: function(partial){
            if (!st || !partial) return;
            if (partial.tl) st.tl = partial.tl.slice();
            if (partial.tr) st.tr = partial.tr.slice();
            if (partial.br) st.br = partial.br.slice();
            if (partial.bl) st.bl = partial.bl.slice();
            
            if ('tlT' in partial) st.tlT = partial.tlT;
            if ('trT' in partial) st.trT = partial.trT;
            if ('brT' in partial) st.brT = partial.brT;
            if ('blT' in partial) st.blT = partial.blT;
            if (partial.tNodes) st.tNodes = partial.tNodes;
            if (partial.rNodes) st.rNodes = partial.rNodes;
            if (partial.bNodes) st.bNodes = partial.bNodes;
            if (partial.lNodes) st.lNodes = partial.lNodes;
            if ('rTL' in partial) st.rTL = Math.max(0, +partial.rTL || 0);
            if ('rTR' in partial) st.rTR = Math.max(0, +partial.rTR || 0);
            if ('rBR' in partial) st.rBR = Math.max(0, +partial.rBR || 0);
            if ('rBL' in partial) st.rBL = Math.max(0, +partial.rBL || 0);
            layoutCrop();
        },
        unmount: function(){
            if (!st) return;
            if (st._canvasObs) { try { st._canvasObs.disconnect(); } catch(_e){} st._canvasObs = null; }
            if (st.overlay && st.overlay.parentNode) st.overlay.parentNode.removeChild(st.overlay);
            if (st.svgEl && st.svgEl.parentNode) st.svgEl.parentNode.removeChild(st.svgEl);
            if (st.wrap && typeof st._savedStyle === 'string') {
                if (st._savedStyle) st.wrap.setAttribute('style', st._savedStyle);
                else st.wrap.removeAttribute('style');
            }
            if (st.img) { st.img.style.webkitClipPath = ''; st.img.style.clipPath = ''; }
            if (st.wrap) {
                var _dc = st.wrap.querySelector(':scope > canvas');
                if (_dc) { _dc.style.webkitClipPath = ''; _dc.style.clipPath = ''; }
            }
            st = null;
        }
    };
})();
