
(function () {
'use strict';

var BP_MOBILE = 768;
var BP_TABLET = 1025;
var HOLD = 200;          
var SVG_NS = 'http://www.w3.org/2000/svg';

var FX_VALID = ['noise', 'ink', 'liquid', 'shatter', 'glitch', 'fold',
                'focus', 'tumble', 'mosaic', 'blinds', 'orbit', 'cards',
                'pop', 'stamp', 'rise', 'vortex'];
var FX_ALIAS = { ripple: 'ink', static: 'glitch', melt: 'fold', tunnel: 'mosaic',
                 wave: 'blinds', bars: 'orbit', fly: 'stamp', converge: 'vortex' };

var FX = {
    noise:   { kind: 'filter', type: 'fractalNoise', freq: 0.022, disp: 430, blur: 0,  flow: 0 },

    ink:     { kind: 'filter', type: 'fractalNoise', freq: 0.02,  disp: 260, blur: 0,  flow: 0, ink: true },
    liquid:  { kind: 'filter', type: 'fractalNoise', freq: 0.006, disp: 480, blur: 6,  flow: 11 },
    shatter: { kind: 'filter', type: 'turbulence',   freq: 0.045, disp: 540, blur: 0,  flow: 0 },

    
    glitch:  { kind: 'filter', type: 'fractalNoise', freq: 0.85,  disp: 420, blur: 0,  flow: 0, glitch: true },

    fold:    { kind: 'fold' },
    focus:   { kind: 'filter', type: 'fractalNoise', freq: 0.02,  disp: 0,   blur: 48, flow: 0 },
    tumble:  { kind: 'xform' },

    
    mosaic:  { kind: 'mosaic' },

    blinds:  { kind: 'fold', vert: true },

    orbit:   { kind: 'blocks' },
    cards:   { kind: 'blocks' },

    
    pop:      { kind: 'cells' },

    stamp:    { kind: 'cells' },
    rise:     { kind: 'cells' },

    vortex:   { kind: 'cells' }
};

function clamp(v, a, b) { return v < a ? a : (v > b ? b : v); }
function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }
var EASES = {
    glide:  function (t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; },
    snap:   easeOutCubic,
    spring: function (t) { var c1 = 0.7, c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); }
};
function now() { return (window.performance && performance.now) ? performance.now() : Date.now(); }
function vpH() { return window.innerHeight || document.documentElement.clientHeight || 1; }

var filterSeq = 0;
function buildFilter(spec) {
    var id = 'impulse-ovfx-f' + (filterSeq++);
    var svg = document.createElementNS(SVG_NS, 'svg');
    svg.setAttribute('data-ovfx-svg', '1');
    svg.setAttribute('width', '0');
    svg.setAttribute('height', '0');
    svg.style.cssText = 'position:absolute;width:0;height:0;overflow:visible;pointer-events:none';
    var flow = spec.flow > 0
        ? '<feOffset in="turb" result="turb" dx="0" dy="0">' +
            '<animate attributeName="dx" values="0;26;0;-26;0" dur="' + spec.flow + 's" repeatCount="indefinite" calcMode="linear"/>' +
            '<animate attributeName="dy" values="-26;0;26;0;-26" dur="' + spec.flow + 's" repeatCount="indefinite" calcMode="linear"/>' +
          '</feOffset>'
        : '';
    if (spec.glitch) {

        
        
        svg.innerHTML =
            '<filter id="' + id + '" x="-50%" y="-50%" width="200%" height="200%">' +
              '<feTurbulence type="fractalNoise" baseFrequency="0.002 ' + spec.freq + '" ' +
                'numOctaves="1" seed="8" stitchTiles="stitch" result="turb"/>' +
              '<feColorMatrix in="SourceGraphic" type="matrix" values="1 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1 0" result="chR"/>' +
              '<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0  0 0 0 0 0  0 0 1 0 0  0 0 0 1 0" result="chB"/>' +
              '<feOffset in="chR" dx="0" dy="0" result="chRo"/>' +
              '<feOffset in="chB" dx="0" dy="0" result="chBo"/>' +
              '<feBlend in="SourceGraphic" in2="chRo" mode="screen" result="m1"/>' +
              '<feBlend in="m1" in2="chBo" mode="screen" result="src"/>' +
              '<feDisplacementMap in="src" in2="turb" scale="0" ' +
                'xChannelSelector="R" yChannelSelector="A"/>' +
            '</filter>';
        return {
            id: id,
            svg: svg,
            disp: svg.querySelector('feDisplacementMap'),
            blur: null,
            turb: svg.querySelector('feTurbulence'),
            offR: svg.querySelectorAll('feOffset')[0],
            offB: svg.querySelectorAll('feOffset')[1]
        };
    }
    svg.innerHTML =
        '<filter id="' + id + '" x="-50%" y="-50%" width="200%" height="200%">' +
          '<feTurbulence type="' + spec.type + '" baseFrequency="' + spec.freq + '" ' +
            'numOctaves="2" seed="6" stitchTiles="stitch" result="turb"/>' +
          flow +
          '<feGaussianBlur in="SourceGraphic" stdDeviation="0" result="src"/>' +
          '<feDisplacementMap in="src" in2="turb" scale="0" ' +
            'xChannelSelector="R" yChannelSelector="G"/>' +
        '</filter>';
    return {
        id: id,
        svg: svg,
        disp: svg.querySelector('feDisplacementMap'),
        blur: svg.querySelector('feGaussianBlur'),
        turb: svg.querySelector('feTurbulence')
    };
}

function parseColor(c) {
    c = String(c || '').trim();
    if (c.charAt(0) === '#') {
        if (c.length === 4) c = '#' + c[1] + c[1] + c[2] + c[2] + c[3] + c[3];
        return [parseInt(c.substr(1, 2), 16) || 0,
                parseInt(c.substr(3, 2), 16) || 0,
                parseInt(c.substr(5, 2), 16) || 0];
    }
    var m = c.match(/rgba?\(([^)]+)\)/);
    if (m) { var p = m[1].split(','); return [parseFloat(p[0]) || 0, parseFloat(p[1]) || 0, parseFloat(p[2]) || 0]; }
    return [128, 128, 128];
}
function mix(c1, c2, t) {
    var a = parseColor(c1), b = parseColor(c2);
    return 'rgb(' + Math.round(a[0] + (b[0] - a[0]) * t) + ',' +
                    Math.round(a[1] + (b[1] - a[1]) * t) + ',' +
                    Math.round(a[2] + (b[2] - a[2]) * t) + ')';
}
function wedgePolygon(a0, a1) {
    var pts = ['50% 50%'];
    var steps = Math.max(2, Math.ceil((a1 - a0) / 12));
    for (var i = 0; i <= steps; i++) {
        var ang = (a0 + (a1 - a0) * i / steps) * Math.PI / 180;
        pts.push((50 + Math.sin(ang) * 150).toFixed(1) + '% ' + (50 - Math.cos(ang) * 150).toFixed(1) + '%');
    }
    return 'polygon(' + pts.join(',') + ')';
}

var PIECE_RECIPES = {
    
    zipper: function (cfg, w, h) {
        var bands = clamp(Math.round(cfg.p1) || 8, 2, 24);
        var rad = clamp(cfg.p2 || 0, 0, 90) * Math.PI / 180;
        var px = -Math.sin(rad), py = Math.cos(rad);
        var ext = Math.abs(w * px) + Math.abs(h * py);
        var thick = ext / bands, len = Math.sqrt(w * w + h * h) * 1.6;
        var cx = w / 2, cy = h / 2, seam = (bands - 1) / 2, out = [];
        for (var k = 0; k < bands; k++) {
            var off = -ext / 2 + (k + 0.5) * thick, side = off >= 0 ? 1 : -1;
            out.push({
                bg: mix(cfg.c1, cfg.c2, bands > 1 ? k / (bands - 1) : 0),
                left: cx + px * off - len / 2, top: cy + py * off - thick / 2,
                width: len, height: thick + 2, br: clamp(cfg.p2 || 0, 0, 90),
                ex: px * side * ext * 1.3, ey: py * side * ext * 1.3,
                ord: seam > 0 ? (seam - Math.abs(k - seam)) / seam * (bands - 1) : 0
            });
        }
        return out;
    },
    
    blinds: function (cfg, w, h) {
        var slats = clamp(Math.round(cfg.p1) || 12, 3, 30);
        var rad = clamp(cfg.p2 || 0, 0, 90) * Math.PI / 180;
        var px = -Math.sin(rad), py = Math.cos(rad);
        var ext = Math.abs(w * px) + Math.abs(h * py);
        var thick = ext / slats, len = Math.sqrt(w * w + h * h) * 1.6;
        var cx = w / 2, cy = h / 2, out = [];
        for (var k = 0; k < slats; k++) {
            var off = -ext / 2 + (k + 0.5) * thick;
            out.push({
                bg: mix(cfg.c1, cfg.c2, slats > 1 ? k / (slats - 1) : 0),
                left: cx + px * off - len / 2, top: cy + py * off - thick / 2,
                width: len, height: thick + 2, br: clamp(cfg.p2 || 0, 0, 90),
                rx: 90, es: 0, ord: k
            });
        }
        return out;
    },
    
    crosscut: function (cfg, w, h) {
        var cols = clamp(Math.round(cfg.p1) || 6, 2, 16);
        var drift = clamp(cfg.p2 || 0, 0, 100) / 100;
        var rows = Math.max(2, Math.round(cols * h / Math.max(w, 1)));
        var cw = w / cols, ch = h / rows, maxIdx = cols + rows - 2, out = [];
        for (var r = 0; r < rows; r++) for (var c = 0; c < cols; c++) {
            var parity = (r + c) % 2;
            out.push({
                bg: mix(cfg.c1, cfg.c2, maxIdx > 0 ? (r + c) / maxIdx : 0),
                left: c * cw - 1, top: r * ch - 1, width: cw + 2, height: ch + 2,
                ey: (parity ? 1 : -1) * h * 1.4, ex: (parity ? 1 : -1) * w * drift * 0.5,
                ord: r + c
            });
        }
        return out;
    },
    
    aperture: function (cfg, w, h) {
        var blades = clamp(Math.round(cfg.p1) || 8, 4, 16);
        var twist = clamp(cfg.p2 || 0, 0, 100) / 100;
        var span = 360 / blades, out = [];
        for (var k = 0; k < blades; k++) {
            var mid = (k + 0.5) * span * Math.PI / 180;
            out.push({
                bg: mix(cfg.c1, cfg.c2, blades > 1 ? k / (blades - 1) : 0),
                clip: wedgePolygon(k * span - 2, (k + 1) * span + 2),
                ox: (50 + Math.sin(mid) * 52).toFixed(1) + '%',
                oy: (50 - Math.cos(mid) * 52).toFixed(1) + '%',
                rz: -(96 + twist * 60), es: 0, ord: k
            });
        }
        return out;
    },
    
    pinwheel: function (cfg, w, h) {
        var blades = clamp(Math.round(cfg.p1) || 8, 3, 16);
        var spin = clamp(cfg.p2 || 0, 0, 100) / 100;
        var span = 360 / blades, out = [];
        for (var k = 0; k < blades; k++) {
            out.push({
                bg: mix(cfg.c1, cfg.c2, blades > 1 ? k / (blades - 1) : 0),
                clip: wedgePolygon(k * span - 1, (k + 1) * span + 1),
                ox: '50%', oy: '50%', rz: -(120 + spin * 600), es: 0, ord: k
            });
        }
        return out;
    },
    
    diamonds: function (cfg, w, h) {
        var g = clamp(Math.round(cfg.p1) || 6, 2, 14);
        var spin = clamp(cfg.p2 || 0, 0, 100) / 100;
        var rows = Math.max(2, Math.round(g * h / Math.max(w, 1)));
        var cw = w / g, ch = h / rows, dia = (cw + ch) * 1.05;
        var total = g * rows, idx = 0, out = [];
        for (var r = 0; r < rows; r++) for (var c = 0; c < g; c++) {
            var ccx = (c + 0.5) * cw, ccy = (r + 0.5) * ch;
            out.push({
                bg: mix(cfg.c1, cfg.c2, total > 1 ? idx / (total - 1) : 0),
                clip: 'polygon(50% 0, 100% 50%, 50% 100%, 0 50%)',
                left: ccx - dia / 2, top: ccy - dia / 2, width: dia, height: dia,
                rz: (idx % 2 ? 1 : -1) * (60 + spin * 240), es: 0, ord: r + c
            });
            idx++;
        }
        return out;
    }
};

var instances = [];
var rafPending = false;
var animRunning = false;

function pickFx(cfg) {
    var fx;
    if (cfg.editor) {
        fx = cfg.fx;
    } else {
        var w = window.innerWidth;
        if (w < BP_MOBILE)      fx = (cfg.fxM != null ? cfg.fxM : cfg.fx);
        else if (w < BP_TABLET) fx = (cfg.fxT != null ? cfg.fxT : cfg.fx);
        else                    fx = cfg.fx;
    }
    if (FX_ALIAS[fx]) fx = FX_ALIAS[fx];
    return FX_VALID.indexOf(fx) !== -1 ? fx : '';
}

function collectBlocks(section) {
    var cont = section.querySelector(':scope > .impulse-container') ||
               section.querySelector('.impulse-container');
    if (!cont) return [section];
    var kids = [];
    for (var i = 0; i < cont.children.length; i++) {
        var k = cont.children[i];
        if (k.nodeType === 1) kids.push(k);
    }
    return kids.length ? kids : [cont];
}

function outermostCells(root) {

    

    var found = root.querySelectorAll('.impulse-grid-cell, .impulse-rows-cell, .impulse-layout-cell, .impulse-reviews-header, .impulse-reviews-rv, .impulse-reviews-card, .impulse-table-item, .impulse-price-item, .impulse-pricing-card, .impulse-acc-reveal-item, .impulse-acc-spot-item, .impulse-unfold-card, .impulse-acc-tile, .isch-dayhdr, .isch-card, .impulse-team-card, .impulse-estimate-block .ime-head, .impulse-estimate-block .ime-sub, .impulse-estimate-block .ime-form > *:not(input[type="hidden"]):not(.ime-msg):not(.ime-vin-msg), .impulse-contact-form > *:not(input[type="hidden"]):not(.impulse-form-message)');
    var cells = [];
    for (var i = 0; i < found.length; i++) {
        var c = found[i], nested = false, p = c.parentNode;
        while (p && p !== root) {
            if (p.classList && (p.classList.contains('impulse-grid-cell') ||
                                p.classList.contains('impulse-rows-cell') ||
                                p.classList.contains('impulse-layout-cell') ||
                                p.classList.contains('impulse-reviews-rv') ||
                                p.classList.contains('impulse-reviews-card') ||
                                p.classList.contains('impulse-table-item') ||
                                p.classList.contains('impulse-pricing-card') ||
                                p.classList.contains('impulse-acc-reveal-item') ||
                                p.classList.contains('impulse-acc-spot-item') ||
                                p.classList.contains('impulse-unfold-card') ||
                                p.classList.contains('impulse-acc-tile') ||
                                p.classList.contains('impulse-team-card') ||
                                p.classList.contains('isch-card'))) { nested = true; break; }
            p = p.parentNode;
        }
        if (!nested) cells.push(c);
    }
    return cells;
}

function selfAnimating(el) {
    return !!(el && el.classList && el.classList.contains('impulse-kinetic-block'));
}
function collectUnits(section) {
    var blocks = collectBlocks(section);
    var units = [];
    for (var i = 0; i < blocks.length; i++) {
        var b = blocks[i];
        if (selfAnimating(b)) continue;
        var cells = outermostCells(b);
        if (cells.length) { for (var j = 0; j < cells.length; j++) { if (!selfAnimating(cells[j])) units.push(cells[j]); } }
        else units.push(b);
    }
    return units.length ? units : [section];
}

function contentTarget(section) {
    return section.querySelector(':scope > .impulse-container') ||
           section.querySelector('.impulse-container') || section;
}

function teardown(inst) {
    if (inst.filter && inst.filter.svg.parentNode) {
        inst.filter.svg.parentNode.removeChild(inst.filter.svg);
    }
    inst.filter = null;
    if (inst.piecesCont && inst.piecesCont.parentNode) {
        inst.piecesCont.parentNode.removeChild(inst.piecesCont);
    }
    inst.piecesCont = null;
    inst.pieceEls = null;
    if (inst.foldHolder && inst.foldHolder.parentNode) {
        inst.foldHolder.parentNode.removeChild(inst.foldHolder);
    }
    inst.foldHolder = null;
    inst.strips = null;
    if (inst.section && inst.section.classList) inst.section.classList.remove('impulse-ovfx-armed');

    
    if (inst.section && inst.section.removeAttribute) inst.section.removeAttribute('data-ovfx-seen');
    if (inst.content) inst.content.style.visibility = '';
    if (inst.section) {
        inst.section.style.filter = '';
        inst.section.style.transform = '';
        inst.section.style.willChange = '';
        inst.section.style.opacity = '';
    }

    
    if (inst.content && inst.content !== inst.section) {
        inst.content.style.filter = '';
        inst.content.style.transform = '';
        inst.content.style.willChange = '';
        inst.content.style.opacity = '';
        inst.content.style.clipPath = '';
    }
    if (inst.blocks) {
        for (var i = 0; i < inst.blocks.length; i++) {
            var b = inst.blocks[i];
            b.style.transform = '';
            b.style.willChange = '';
            b.style.opacity = '';
            b.style.transformStyle = '';
            b.style.backfaceVisibility = '';
        }
    }
}

var CHART_ANIM_SEL = '.impulse-pie-sweep-wrap,.impulse-chart-line-path,.impulse-chart-line-area,.impulse-chart-slice';
function eachChartAnim(chart, fn) {
    var els = chart.querySelectorAll(CHART_ANIM_SEL);
    for (var j = 0; j < els.length; j++) {
        var anims = els[j].getAnimations ? els[j].getAnimations() : [];
        for (var k = 0; k < anims.length; k++) { try { fn(anims[k]); } catch (e) {} }
    }
}
function armChartAnims(sec) {
    if (!sec || !sec.querySelectorAll) return;
    var charts = sec.querySelectorAll('.impulse-chart[data-animate="1"]');
    for (var i = 0; i < charts.length; i++) {
        var chart = charts[i];
        if (chart.classList.contains('impulse-chart-anim-armed')) continue;
        chart.classList.add('impulse-chart-anim-armed');

        
        
        eachChartAnim(chart, function (a) { a.pause(); a.currentTime = 0; });
    }
}
function runChartAnims(sec) {
    if (!sec || !sec.querySelectorAll) return;
    var charts = sec.querySelectorAll('.impulse-chart.impulse-chart-anim-armed');
    for (var i = 0; i < charts.length; i++) {
        
        eachChartAnim(charts[i], function (a) { a.currentTime = 0; a.play(); });
        charts[i].classList.remove('impulse-chart-anim-armed');
    }
}

function finishReveal(inst) {

    

    
    
    teardown(inst);
}

function prepare(inst) {
    teardown(inst);
    inst.triggered = false;
    inst.done = false;
    var fx = pickFx(inst.cfg);
    inst.fx = fx;
    if (!fx) return;
    
    armChartAnims(inst.section);
    
    inst.section.classList.add('impulse-ovfx-armed');
    var spec = FX[fx];
    inst.spec = spec;
    inst.duration = clamp(2200 - (inst.cfg.speed || 5) * 200, 300, 2200);

    inst.content = contentTarget(inst.section);

    if (spec.kind === 'filter') {
        var p2 = clamp(inst.cfg.p2 || 0, 0, 100) / 100;
        var fspec = {
            type: spec.type,
            freq: (spec.freq * (0.45 + p2 * 1.7)).toFixed(4),
            flow: spec.flow,
            glitch: spec.glitch
        };
        inst.filter = buildFilter(fspec);
        inst.content.parentNode.insertBefore(inst.filter.svg, inst.content);
        inst.content.style.filter = 'url(#' + inst.filter.id + ')';
        inst.maxDisp = (clamp(inst.cfg.p1 || 0, 0, 100) / 100) * spec.disp;
        inst.maxBlur = spec.blur * (spec.disp ? 1 : (clamp(inst.cfg.p1 || 0, 0, 100) / 100));
        inst.depth  = (clamp(inst.cfg.p3 || 0, 0, 100) / 100) * 600;
        if (spec.ink) {
            
            inst.maxBlur = (clamp(inst.cfg.p3 || 0, 0, 100) / 100) * 18;
            inst.depth = 0;
        }
        if (spec.glitch) {
            
            inst.split = (clamp(inst.cfg.p3 || 0, 0, 100) / 100) * 26;
            inst.depth = 0;
            inst._gNext = 0;
            inst._gAmp = 1;
        }
    } else if (spec.kind === 'fold') {

        
        
        var fr = inst.content.getBoundingClientRect();
        var vert = !!spec.vert;
        if ((vert ? fr.width : fr.height) < 40) { inst.fx = ''; return; }

        

        

        
        
        inst.foldWhole = !!inst.content.querySelector('canvas,video,iframe,.impulse-kinetic-block');
        inst.foldVert = vert;
        inst.strips = null;
        if (inst.foldWhole) {
            inst.content.style.willChange = 'transform';
            inst.content.style.backfaceVisibility = 'hidden';
        } else {
            var nStrips = vert
                ? 6 + Math.round((clamp(inst.cfg.p1 || 0, 0, 100) / 100) * 10)
                : 3 + Math.round((clamp(inst.cfg.p1 || 0, 0, 100) / 100) * 5);
            var sh = (vert ? fr.width : fr.height) / nStrips;
            var holder = document.createElement('div');
            holder.setAttribute('data-ovfx-fold', '1');
            holder.style.cssText = 'position:absolute;left:' + inst.content.offsetLeft + 'px;top:' + inst.content.offsetTop +
                'px;width:' + fr.width + 'px;height:' + fr.height + 'px;pointer-events:none;z-index:5;perspective:1400px';
            var strips = [];
            for (var sk = 0; sk < nStrips; sk++) {
                var strip = document.createElement('div');
                var band = (sk * sh) + 'px;' + (vert ? 'width' : 'height') + ':' + (sh + (sk < nStrips - 1 ? 1 : 0)) + 'px;';
                strip.style.cssText = 'position:absolute;overflow:hidden;backface-visibility:hidden;will-change:transform,filter;' +
                    (vert
                        ? 'top:0;bottom:0;left:' + band + 'transform-origin:50% 50%;'
                        : 'left:0;right:0;top:' + band + 'transform-origin:50% ' + (sk % 2 ? '100%' : '0%') + ';');
                var clone = inst.content.cloneNode(true);
                clone.style.cssText = 'position:absolute;' +
                    (vert ? 'top:0;left:' + (-sk * sh) + 'px;' : 'left:0;top:' + (-sk * sh) + 'px;') +
                    'width:' + fr.width + 'px;margin:0;';
                clone.setAttribute('aria-hidden', 'true');
                var ids = clone.querySelectorAll('[id]');
                for (var ii = 0; ii < ids.length; ii++) ids[ii].removeAttribute('id');
                strip.appendChild(clone);
                holder.appendChild(strip);
                strips.push(strip);
            }
            inst.content.style.visibility = 'hidden';
            inst.content.parentNode.insertBefore(holder, inst.content.nextSibling);
            inst.foldHolder = holder;
            inst.strips = strips;
        }
    } else if (spec.kind === 'mosaic') {
        var mr = inst.content.getBoundingClientRect();
        if (mr.width < 40 || mr.height < 40) { inst.fx = ''; return; }
        var cols = 4 + Math.round((clamp(inst.cfg.p1 || 0, 0, 100) / 100) * 8);
        var rows = Math.max(3, Math.round(cols * mr.height / mr.width));
        var order = [];
        for (var ti = 0; ti < cols * rows; ti++) order.push(ti);
        for (var sj = order.length - 1; sj > 0; sj--) {
            var rj = Math.floor(Math.random() * (sj + 1));
            var tmp = order[sj]; order[sj] = order[rj]; order[rj] = tmp;
        }
        inst.mosaic = { cols: cols, rows: rows, w: mr.width, h: mr.height, order: order };
        inst.content.style.willChange = 'clip-path, transform';
    } else if (spec.kind === 'blocks' || spec.kind === 'cells' || spec.kind === 'xform') {

        

        
        
        var units = collectUnits(inst.section);
        inst.blocks = units;
        var sr = inst.section.getBoundingClientRect();
        var scx = sr.left + sr.width / 2, scy = sr.top + sr.height / 2;
        var radial = (fx === 'vortex'), metric = [];
        for (var ce = 0; ce < units.length; ce++) {
            var cl = units[ce], cr = cl.getBoundingClientRect();
            var mcx = cr.left + cr.width / 2, mcy = cr.top + cr.height / 2;
            cl._ox = mcx - scx; cl._oy = mcy - scy;       
            metric.push(radial ? Math.sqrt(cl._ox * cl._ox + cl._oy * cl._oy)
                               : (mcx - sr.left) + (mcy - sr.top));
            cl.style.willChange = 'transform, opacity';
            cl.style.transformStyle = 'preserve-3d';
            cl.style.backfaceVisibility = 'hidden';
        }
        var cmn = Math.min.apply(null, metric), cmx = Math.max.apply(null, metric), crg = (cmx - cmn) || 1;
        for (var cn = 0; cn < units.length; cn++) units[cn]._stag = (metric[cn] - cmn) / crg;

        

        
        for (var ovi = 0; ovi < units.length; ovi++) {
            var _u = units[ovi], _n = _u, _ord = null, _dly = 0, _off = false;
            while (_n && _n.dataset) {
                if (_ord === null && _n.dataset.revealOrder != null && _n.dataset.revealOrder !== '') _ord = parseFloat(_n.dataset.revealOrder);
                if (!_dly && _n.dataset.revealDelay) _dly = parseFloat(_n.dataset.revealDelay) || 0;
                if (_n.dataset.revealOff === '1') _off = true;
                if (_n === inst.section) break;
                _n = _n.parentElement;
            }
            _u._revealDelayMs = _dly;
            _u._revealOff = _off;
            if (_ord !== null && !isNaN(_ord)) _u._stag = Math.max(0, Math.min(1, _ord / 10));
        }
        var _kept = units.filter(function (u) { return !u._revealOff; });
        if (_kept.length && _kept.length !== units.length) { units = _kept; inst.blocks = units; }
    } else if (spec.kind === 'pieces') {
        var pr = inst.section.getBoundingClientRect();
        var cont = document.createElement('div');
        cont.setAttribute('data-ovfx-pieces', '1');
        cont.style.cssText = 'position:absolute;inset:0;z-index:8;pointer-events:none;overflow:hidden;perspective:1200px';
        var list = (PIECE_RECIPES[fx] || PIECE_RECIPES.zipper)(inst.cfg, pr.width, pr.height);
        var omin = Infinity, omax = -Infinity, j;
        for (j = 0; j < list.length; j++) {
            var ov = (list[j].ord != null) ? list[j].ord : j;
            if (ov < omin) omin = ov;
            if (ov > omax) omax = ov;
        }
        var orange = (omax - omin) || 1;
        var els = [];
        for (j = 0; j < list.length; j++) {
            var pd = list[j];
            var d = document.createElement('div');
            d.style.position = 'absolute';
            d.style.background = pd.bg;
            d.style.backfaceVisibility = 'hidden';
            if (pd.clip) { d.style.clipPath = pd.clip; d.style.webkitClipPath = pd.clip; }
            if (pd.width != null) {
                d.style.left = pd.left + 'px'; d.style.top = pd.top + 'px';
                d.style.width = pd.width + 'px'; d.style.height = pd.height + 'px';
            } else {
                d.style.left = '0'; d.style.top = '0'; d.style.right = '0'; d.style.bottom = '0';
            }
            d.style.transformOrigin = (pd.ox || '50%') + ' ' + (pd.oy || '50%');
            d._d = pd;
            d._ord = (((pd.ord != null) ? pd.ord : j) - omin) / orange * (list.length - 1);
            cont.appendChild(d);
            els.push(d);
        }
        inst.section.appendChild(cont);
        inst.piecesCont = cont;
        inst.pieceEls = els;
        inst.n = list.length;
    }

    

    
    render(inst, 0);
}

function render(inst, lin) {
    var fx = inst.fx, c = inst.cfg;
    var ease = EASES[c.ease] || EASES.spring;
    var p1 = clamp(c.p1 || 0, 0, 100) / 100;
    var p2 = clamp(c.p2 || 0, 0, 100) / 100;
    var p3 = clamp(c.p3 || 0, 0, 100) / 100;

    if (inst.spec.kind === 'pieces') {
        var els = inst.pieceEls, np = inst.n;
        var pstag = p3 * 0.85, pspan = 1 - pstag;
        if (pspan < 0.06) pspan = 0.06;
        for (var pi = 0; pi < els.length; pi++) {
            var pel = els[pi], pd = pel._d;
            var pdelay = np > 1 ? pstag * (pel._ord / (np - 1)) : 0;
            var pp = ease(clamp((lin - pdelay) / pspan, 0, 1));
            pel.style.transform =
                'translate(' + ((pd.ex || 0) * pp).toFixed(1) + 'px,' + ((pd.ey || 0) * pp).toFixed(1) + 'px) ' +
                'rotate(' + ((pd.br || 0) + (pd.rz || 0) * pp).toFixed(2) + 'deg) ' +
                'rotateX(' + ((pd.rx || 0) * pp).toFixed(2) + 'deg) ' +
                'rotateY(' + ((pd.ry || 0) * pp).toFixed(2) + 'deg) ' +
                'scale(' + (1 + ((pd.es != null ? pd.es : 1) - 1) * pp).toFixed(3) + ')';
        }
        return;
    }

    if (inst.spec.kind === 'fold') {
        
        var swing = inst.foldVert ? (70 + p2 * 35) : (55 + p2 * 70);
        if (inst.foldWhole || !inst.strips) {
            var wp = ease(lin), wd = 1 - wp;
            inst.content.style.transformOrigin = '50% 0%';
            inst.content.style.transform = lin >= 1 ? '' :
                'perspective(1400px) rotateX(' + (-swing * wd).toFixed(2) + 'deg)';
            inst.content.style.opacity = clamp(0.15 + 0.85 * wp, 0, 1).toFixed(3);
            return;
        }
        if (!inst.strips.length) return;   
        var ns = inst.strips.length;
        var fstag = 0.15 + p3 * 0.6, fspan = 1 - fstag;
        if (fspan < 0.06) fspan = 0.06;
        for (var si = 0; si < ns; si++) {
            var sph = ns > 1 ? si / (ns - 1) : 0;
            var sp2 = ease(clamp((lin - fstag * sph) / fspan, 0, 1));
            var sd = 1 - sp2;
            var st = inst.strips[si];
            if (inst.foldVert) {
                
                st.style.transform = 'rotateY(' + (swing * sd).toFixed(2) + 'deg)';
            } else {
                var dir = si % 2 ? 1 : -1;   
                st.style.transform = 'rotateX(' + (dir * swing * sd).toFixed(2) + 'deg)';
            }
            
            st.style.filter = sd > 0.005 ? 'brightness(' + (1 - 0.55 * sd).toFixed(3) + ')' : '';
            st.style.opacity = clamp(0.1 + 0.9 * sp2, 0, 1).toFixed(3);
        }
        if (lin >= 1) {
            
            inst.content.style.visibility = '';
        }
        return;
    }

    if (inst.spec.kind === 'mosaic') {
        
        var mo = inst.mosaic;
        if (!mo) return;                   
        var total = mo.cols * mo.rows;
        var mstag = 0.25 + p3 * 0.6, mspan = 1 - mstag;
        if (mspan < 0.06) mspan = 0.06;
        if (lin >= 1) {
            inst.content.style.clipPath = '';
            inst.content.style.transform = '';
            inst.content.style.opacity = '';
            return;
        }
        var cw = mo.w / mo.cols, chh = mo.h / mo.rows, path = '';
        for (var mi = 0; mi < total; mi++) {
            var mph = total > 1 ? mo.order[mi] / (total - 1) : 0;
            var mp = ease(clamp((lin - mstag * mph) / mspan, 0, 1));
            if (mp <= 0.002) continue;
            if (mp > 1) mp = 1;
            var col = mi % mo.cols, row = (mi / mo.cols) | 0;
            var tw = cw * mp, th = chh * mp;
            var x0 = (col * cw + (cw - tw) / 2).toFixed(1), y0 = (row * chh + (chh - th) / 2).toFixed(1);
            path += 'M' + x0 + ' ' + y0 + 'h' + tw.toFixed(1) + 'v' + th.toFixed(1) + 'h-' + tw.toFixed(1) + 'Z';
        }
        inst.content.style.clipPath = path ? "path('" + path + "')" : 'inset(100%)';
        var mzp = ease(lin);
        inst.content.style.transform = 'perspective(1200px) translateZ(' + (-(40 + p2 * 260) * (1 - mzp)).toFixed(1) + 'px)';
        return;
    }

    if (inst.spec.kind === 'filter' && !inst.filter) return;   

    if (inst.spec.kind === 'filter' && inst.spec.ink) {

        

        var pk = ease(lin), dk = clamp(1 - pk, 0, 1);
        var rr = 4 + pk * 146;                                   
        inst.content.style.clipPath = lin >= 1 ? '' : 'circle(' + rr.toFixed(1) + '% at 50% 50%)';
        var edge = lin >= 1 ? 0 : Math.min(1, dk * 3);           
        if (inst.maxDisp) inst.filter.disp.setAttribute('scale', (inst.maxDisp * (0.35 + 0.65 * dk) * edge).toFixed(1));
        if (inst.maxBlur && inst.filter.blur) inst.filter.blur.setAttribute('stdDeviation', (inst.maxBlur * dk).toFixed(2));

        
        inst.content.style.opacity = clamp(pk * 4, 0, 1).toFixed(3);
        return;
    }

    if (inst.spec.kind === 'filter' && inst.spec.glitch) {

        
        var pg = ease(lin), dg = clamp(1 - pg, 0, 1);
        var tnow = now();
        if (tnow >= inst._gNext) {
            inst._gNext = tnow + 40 + Math.random() * 90;
            inst._gAmp = Math.random() < 0.25 ? 0.05 : (0.3 + Math.random() * 0.7);
        }
        var amp = dg * inst._gAmp;
        inst.filter.disp.setAttribute('scale', (inst.maxDisp * amp).toFixed(1));
        if (inst.filter.offR) {
            var sp = inst.split * amp;
            inst.filter.offR.setAttribute('dx', sp.toFixed(1));
            inst.filter.offB.setAttribute('dx', (-sp).toFixed(1));
        }
        var jx = (Math.random() * 2 - 1) * 14 * amp;
        inst.content.style.transform = lin >= 1 ? '' : 'translate3d(' + jx.toFixed(1) + 'px,0,0)';
        inst.content.style.opacity = clamp(pg * 3, 0, 1).toFixed(3);   
        return;
    }

    if (inst.spec.kind === 'filter') {
        var pf = ease(lin), df = 1 - pf;
        var dDisp = clamp(df, 0, 1);      
        if (inst.maxDisp) inst.filter.disp.setAttribute('scale', (inst.maxDisp * dDisp).toFixed(1));
        if (inst.maxBlur) inst.filter.blur.setAttribute('stdDeviation', (inst.maxBlur * dDisp).toFixed(2));
        
        var dz = inst.depth * df;
        var rx = 14 * p3 * df;
        var ty = 0;
        inst.content.style.transform =
            'perspective(1500px) translate3d(0,' + ty.toFixed(1) + 'px,' + (-dz).toFixed(1) + 'px) ' +
            'rotateX(' + rx.toFixed(2) + 'deg)';
        inst.content.style.opacity = (fx === 'static') ? clamp(pf * 3, 0, 1).toFixed(3) : '';   
        return;
    }

    

    var units = inst.blocks, n = units.length;
    var cellFamily = (fx === 'pop' || fx === 'stamp' || fx === 'rise' || fx === 'vortex');
    var stag = cellFamily ? (0.12 + p3 * 0.62) : (0.2 + p3 * 0.58);
    var span = 1 - stag;
    if (span < 0.06) span = 0.06;
    for (var i = 0; i < n; i++) {
        var u = units[i];
        var ph = (u._stag != null) ? u._stag : (n > 1 ? i / (n - 1) : 0);
        var _dph = (u._revealDelayMs && inst.duration) ? (u._revealDelayMs / inst.duration) : 0;
        var bp = ease(clamp((lin - stag * ph - _dph) / span, 0, 1));
        var bd = 1 - bp, alt = (i % 2 ? 1 : -1), t;
        if (fx === 'pop') {
            var st0 = 0.25 - p1 * 0.22;        
            t = 'perspective(1100px) translateZ(' + (-(150 + p2 * 700) * bd).toFixed(1) + 'px) ' +
                'scale(' + Math.max(0, st0 + (1 - st0) * bp).toFixed(3) + ')';
        } else if (fx === 'stamp') {

            
            var sth = (250 + p1 * 650) * bd;
            var stt = (((i * 37) % 13) - 6) * (p2 * 0.9 + 0.1);
            t = 'perspective(1000px) translateZ(' + sth.toFixed(1) + 'px) ' +
                'rotate(' + (stt * bd).toFixed(2) + 'deg)';
        } else if (fx === 'rise') {
            t = 'perspective(1200px) translate3d(0,' + ((150 + p1 * 350) * bd).toFixed(1) + 'px,' +
                (-(180 + p2 * 500) * bd).toFixed(1) + 'px) rotateX(' + (24 * bd).toFixed(1) + 'deg)';
        } else if (fx === 'vortex') {

            
            var vth = (0.6 + p1 * 2.2) * Math.PI * bd;          
            var vgrow = 1 + (0.3 + p2 * 1.4) * bd;              
            var vox = (u._ox || 0), voy = (u._oy || 0);
            var vc = Math.cos(vth), vs = Math.sin(vth);
            var vx = (vox * vc - voy * vs) * vgrow - vox;
            var vy = (vox * vs + voy * vc) * vgrow - voy;
            t = 'perspective(1200px) translate3d(' + vx.toFixed(1) + 'px,' + vy.toFixed(1) + 'px,' +
                (-(100 + p2 * 260) * bd).toFixed(1) + 'px) ' +
                'rotate(' + (vth * 180 / Math.PI * 0.35 * bd).toFixed(2) + 'deg)';
        } else if (fx === 'orbit') {

            
            var oth = (40 + p1 * 70) * bd * Math.PI / 180;
            var orad = 350 + p2 * 850;
            t = 'perspective(1400px) translate3d(' + (Math.sin(oth) * orad).toFixed(1) + 'px,0,' +
                (-(1 - Math.cos(oth)) * orad).toFixed(1) + 'px) ' +
                'rotateY(' + (-(oth * 180 / Math.PI) * 0.8).toFixed(2) + 'deg)';
        } else if (fx === 'cards') {
            var flip = 80 + p2 * 100;
            t = 'perspective(1200px) translateZ(' + (-120 * bd).toFixed(1) + 'px) ' +
                'rotateX(' + (flip * bd).toFixed(2) + 'deg)';
        } else if (fx === 'tumble') {
            var ang = 20 + p1 * 75, tz = 200 + p2 * 900, tsp = p3 * 60;
            t = 'perspective(1400px) rotateX(' + (ang * bd).toFixed(2) + 'deg) ' +
                'rotateZ(' + (alt * tsp * bd).toFixed(2) + 'deg) ' +
                'translateZ(' + (-tz * bd).toFixed(1) + 'px)';
        } else {  
            var depth = 600 + p1 * 2400, nsp = p3 * 220;
            t = 'perspective(1100px) translate3d(' + ((u._ox || 0) * 0.4 * bd).toFixed(1) + 'px,' +
                ((u._oy || 0) * 0.4 * bd).toFixed(1) + 'px,' + (-depth * bd).toFixed(1) + 'px) ' +
                'rotateZ(' + (alt * nsp * bd).toFixed(2) + 'deg)';
        }
        u.style.transform = t;

        

        
        u.style.opacity = clamp(bp * 1.35, 0, 1).toFixed(3);
    }
}

function ensureAnim() {
    if (!animRunning) { animRunning = true; requestAnimationFrame(animateFrame); }
}
function animateFrame(ts) {
    var active = false;
    for (var i = 0; i < instances.length; i++) {
        var inst = instances[i];
        if (!inst.fx || !inst.triggered || inst.done) continue;
        var lin = clamp((ts - inst.t0 - HOLD) / inst.duration, 0, 1);

        

        try {
            render(inst, lin);
            if (lin >= 1) {
                inst.done = true;
                finishReveal(inst);      
                runChartAnims(inst.section);  

                
                inst.section.classList.remove('impulse-ovfx-armed');
                try { inst.section.dispatchEvent(new CustomEvent('impulse:ovfx-settled', { bubbles: false })); } catch (eK) {}
            } else {
                active = true;
            }
        } catch (e) {
            inst.done = true;
            try { teardown(inst); } catch (e2) {}
        }
    }
    if (active) requestAnimationFrame(animateFrame);
    else animRunning = false;
}

function checkTriggers() {
    rafPending = false;
    try { ovfxRescue(); } catch (e) {}   
    for (var i = 0; i < instances.length; i++) {
        var inst = instances[i];
        if (!inst.fx) continue;
        var r = inst.section.getBoundingClientRect();
        var vh = vpH();
        if (!inst.triggered) {
            if (r.top < vh * 0.85 && r.bottom > vh * 0.15) {
                inst.triggered = true;
                inst.done = false;
                inst.t0 = now();
                ensureAnim();
            }
        } else if (inst.done && (r.bottom < vh * 0.05 || r.top > vh * 0.95)) {
            inst.triggered = false;
            inst.done = false;
            prepare(inst);           
        }
    }
}

function ovfxRescue() {
    var vh = vpH();
    var armed = document.querySelectorAll('.impulse-ovfx-armed');
    for (var i = 0; i < armed.length; i++) {
        var sec = armed[i];
        var r = sec.getBoundingClientRect();
        if (r.bottom < 0 || r.top > vh) continue;          
        var seen = parseFloat(sec.getAttribute('data-ovfx-seen') || '0') || 0;
        var t = now();
        if (!seen) { sec.setAttribute('data-ovfx-seen', String(t)); continue; }
        if (t - seen < 1200) continue;                      
        sec.classList.remove('impulse-ovfx-armed');
        sec.removeAttribute('data-ovfx-seen');
        
        var units = collectUnits(sec);
        var ct = contentTarget(sec);
        for (var j = 0; j <= units.length; j++) {
            var u = j < units.length ? units[j] : ct;
            if (!u || !u.style) continue;
            u.style.transform = '';
            u.style.opacity = '';
            u.style.willChange = '';
        }
        try { sec.dispatchEvent(new CustomEvent('impulse:ovfx-settled', { bubbles: false })); } catch (e) {}
    }
}

function onScroll() {

    

    
    if (!rafPending) {
        rafPending = true;
        requestAnimationFrame(checkTriggers);
        setTimeout(function () { if (rafPending) checkTriggers(); }, 220);
    }
}
setInterval(function () { try { checkTriggers(); } catch (eI) {} }, 900);

var resizeTimer;
function onResize() {
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(function () {
        for (var i = 0; i < instances.length; i++) {
            var inst = instances[i];
            if (!inst.done) prepare(inst);
        }
        checkTriggers();
    }, 160);
}

function initAll() {
    for (var k = 0; k < instances.length; k++) teardown(instances[k]);
    instances = [];
    var els = document.querySelectorAll('.impulse-overlay-fx');

    
    var lateBoot = !!(window.performance && performance.now && performance.now() > 2500);
    for (var i = 0; i < els.length; i++) {
        var c = els[i];
        var raw = c.getAttribute('data-ovfx');
        if (!raw) continue;
        var cfg;
        try { cfg = JSON.parse(raw); } catch (e) { continue; }
        var section = c.parentNode;
        if (!section || section.nodeType !== 1) continue;
        var inst = {
            carrier: c, section: section, cfg: cfg, fx: '', spec: null,
            filter: null, blocks: null, piecesCont: null, pieceEls: null,
            triggered: false, done: false,
            t0: 0, duration: 1200, n: 0, maxDisp: 0, maxBlur: 0, depth: 0
        };
        instances.push(inst);

        

        

        
        if (lateBoot && !cfg.editor) {
            var lvr = section.getBoundingClientRect();
            var lvh = vpH();
            if (lvr.top < lvh && lvr.bottom > 0) { inst.triggered = true; inst.done = true; continue; }
        }
        prepare(inst);
    }
}

function boot() {
    initAll();
    var anyEditor = false, i;
    for (i = 0; i < instances.length; i++) {
        if (instances[i].cfg && instances[i].cfg.editor) { anyEditor = true; break; }
    }

    

    
    
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onResize);
    if (anyEditor) {

        
        document.addEventListener('scroll', onScroll, { passive: true, capture: true });
    }
    checkTriggers();
}

function play(sectionEl) {
    if (!sectionEl) { playAll(); return; }
    var inst = null, idx = -1, i;
    for (i = 0; i < instances.length; i++) {
        if (instances[i].section === sectionEl) { inst = instances[i]; idx = i; break; }
    }
    var carrier = sectionEl.querySelector(':scope > .impulse-overlay-fx');
    if (!carrier) {                       
        if (inst) { teardown(inst); instances.splice(idx, 1); }
        return;
    }
    var cfg;
    try { cfg = JSON.parse(carrier.getAttribute('data-ovfx')); } catch (e) { return; }
    if (!inst) {
        inst = { carrier: carrier, section: sectionEl, cfg: cfg, fx: '', spec: null,
                 filter: null, blocks: null, piecesCont: null, pieceEls: null,
                 triggered: false, done: false, t0: 0, duration: 1200, n: 0,
                 maxDisp: 0, maxBlur: 0, depth: 0 };
        instances.push(inst);
    } else {
        inst.cfg = cfg;                   
    }
    prepare(inst);                        
    if (inst.fx) { inst.triggered = true; inst.done = false; inst.t0 = now(); ensureAnim(); }
}

function playAll() {
    initAll();
    var any = false;
    for (var i = 0; i < instances.length; i++) {
        var inst = instances[i];
        if (inst.fx) { inst.triggered = true; inst.done = false; inst.t0 = now(); any = true; }
    }
    if (any) ensureAnim();
}

window.ImpulseOverlayFX = { init: initAll, reinit: initAll, play: play };

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', boot);
} else {
    boot();
}

})();
