
(function() {
    'use strict';

    

    
    var _trackCache = new WeakMap();

    function getTrack(el) {
        if (_trackCache.has(el)) return _trackCache.get(el);
        var raw = el.getAttribute('data-keyframes');
        if (!raw) { _trackCache.set(el, null); return null; }
        var parsed = null;
        try { parsed = JSON.parse(raw); } catch (_) {}
        if (!Array.isArray(parsed) || parsed.length < 2) { _trackCache.set(el, null); return null; }
        
        parsed.sort(function(a, b) { return (a.t || 0) - (b.t || 0); });
        _trackCache.set(el, parsed);
        return parsed;
    }


    

    

    
    function getTrackedElements(screenEl) {
        if (!screenEl) return [];
        var nodes = screenEl.querySelectorAll('[data-keyframes]');
        var out = [];
        if (screenEl.hasAttribute('data-keyframes') && getTrack(screenEl)) {
            out.push(screenEl);
        }
        for (var i = 0; i < nodes.length; i++) {
            if (getTrack(nodes[i])) out.push(nodes[i]);
        }
        return out;
    }

    function screenHasTimeline(screenEl) {
        if (!screenEl) return false;

        
        if (screenEl.hasAttribute('data-keyframes')) return true;
        return screenEl.querySelector('[data-keyframes]') !== null;
    }

    


    
    var EASINGS = {
        linear:     function(t) { return t; },
        easeIn:     function(t) { return t * t; },
        easeOut:    function(t) { return 1 - (1 - t) * (1 - t); },
        easeInOut:  function(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; },
        
        backIn:     function(t) { var c1 = 1.70158, c3 = c1 + 1; return c3 * t * t * t - c1 * t * t; },
        backOut:    function(t) { var c1 = 1.70158, c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); },
        backInOut:  function(t) {
            var c2 = 2.5949095;
            return t < 0.5
                ? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
                : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (2 * t - 2) + c2) + 2) / 2;
        },
        
        bounceOut: function(t) {
            var n1 = 7.5625, d1 = 2.75;
            if (t < 1 / d1)       return n1 * t * t;
            else if (t < 2 / d1)  return n1 * (t -= 1.5 / d1) * t + 0.75;
            else if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + 0.9375;
            else                  return n1 * (t -= 2.625 / d1) * t + 0.984375;
        },
        
        spring: function(t) {
            if (t === 0 || t === 1) return t;
            return 1 - Math.cos(t * Math.PI * 2.2) * Math.exp(-t * 4);
        }
    };

    function lerp(a, b, t) { return a + (b - a) * t; }

    
    function parseHex(hex) {
        if (typeof hex !== 'string') return null;
        var h = hex.trim();
        if (h.charAt(0) !== '#') return null;
        h = h.slice(1);
        if (h.length === 3) {
            return {
                r: parseInt(h[0] + h[0], 16),
                g: parseInt(h[1] + h[1], 16),
                b: parseInt(h[2] + h[2], 16)
            };
        }
        if (h.length === 6) {
            return {
                r: parseInt(h.slice(0, 2), 16),
                g: parseInt(h.slice(2, 4), 16),
                b: parseInt(h.slice(4, 6), 16),
                a: 1
            };
        }
        return null;
    }

    
    
    function parseColor(str) {
        if (!str || typeof str !== 'string') return null;
        if (str.charAt(0) === '#') return parseHex(str);
        var m = str.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?/);
        if (!m) return null;
        return {
            r: parseInt(m[1], 10),
            g: parseInt(m[2], 10),
            b: parseInt(m[3], 10),
            a: m[4] !== undefined ? parseFloat(m[4]) : 1
        };
    }
    function toRgb(c) {
        if (c.a !== undefined && c.a < 1) {
            return 'rgba(' + Math.round(c.r) + ',' + Math.round(c.g) + ',' + Math.round(c.b) + ',' + (Math.round(c.a * 1000) / 1000) + ')';
        }
        return 'rgb(' + Math.round(c.r) + ',' + Math.round(c.g) + ',' + Math.round(c.b) + ')';
    }
    function lerpColor(ah, bh, t) {
        var a = parseColor(ah), b = parseColor(bh);
        if (!a || !b) return t < 0.5 ? ah : bh;

        return toRgb({
            r: lerp(a.r, b.r, t),
            g: lerp(a.g, b.g, t),
            b: lerp(a.b, b.b, t),
            a: lerp(a.a === undefined ? 1 : a.a, b.a === undefined ? 1 : b.a, t)
        });
    }


    function sampleTrack(track, p) {
        var n = track.length;
        if (p <= track[0].t) return track[0].props || {};
        if (p >= track[n - 1].t) return track[n - 1].props || {};
        
        var lo = 0, hi = n - 1;
        for (var i = 0; i < n - 1; i++) {
            if (track[i].t <= p && track[i + 1].t >= p) { lo = i; hi = i + 1; break; }
        }
        var a = track[lo], b = track[hi];
        var span = (b.t - a.t) || 1;
        var localT = (p - a.t) / span;
        var easeFn = EASINGS[b.ease || 'linear'] || EASINGS.linear;
        var eased = easeFn(localT);
        var ap = a.props || {}, bp = b.props || {};
        var out = {};
        
        var keys = {};
        for (var k in ap) keys[k] = true;
        for (var k2 in bp) keys[k2] = true;
        for (var k3 in keys) {
            var av = ap[k3], bv = bp[k3];
            
            if (av === undefined) { out[k3] = bv; continue; }
            if (bv === undefined) { out[k3] = av; continue; }
            if (typeof av === 'number' && typeof bv === 'number') {
                out[k3] = lerp(av, bv, eased);
            } else if (k3 === 'color' || k3 === 'bgColor' || k3 === 'background_color') {
                out[k3] = lerpColor(String(av), String(bv), eased);
            } else {
                out[k3] = eased < 0.5 ? av : bv;
            }
        }
        return out;
    }

    

    var TRANSFORM_KEYS = ['tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'scale'];

    function buildTransform(p) {
        var tx = p.tx || 0, ty = p.ty || 0, tz = p.tz || 0;
        var rx = p.rx || 0, ry = p.ry || 0, rz = p.rz || 0;
        var sc = (p.scale === undefined ? 1 : p.scale);
        var parts = ['translate3d(' + tx + 'px,' + ty + 'px,' + tz + 'px)'];
        if (rx) parts.push('rotateX(' + rx + 'deg)');
        if (ry) parts.push('rotateY(' + ry + 'deg)');
        if (rz) parts.push('rotateZ(' + rz + 'deg)');
        if (sc !== 1) parts.push('scale(' + sc + ')');
        return parts.join(' ');
    }


    

    

    
    function applyInlineDeep(el, cssProp, value) {
        el.style[cssProp] = value;
        var inner = el.querySelectorAll('[style]');
        for (var i = 0; i < inner.length; i++) {
            if (inner[i].style[cssProp] !== undefined && inner[i].style[cssProp] !== '') {
                inner[i].style[cssProp] = value;
            }
        }
    }

    function applyProps(el, props) {

        
        var hasTf = false;
        for (var i = 0; i < TRANSFORM_KEYS.length; i++) {
            if (props[TRANSFORM_KEYS[i]] !== undefined) { hasTf = true; break; }
        }
        if (hasTf) el.style.transform = buildTransform(props);

        if (props.opacity !== undefined) el.style.opacity = props.opacity;
        if (props.blur    !== undefined) el.style.filter  = 'blur(' + props.blur + 'px)';

        
        if (props.color   !== undefined) applyInlineDeep(el, 'color', props.color);

        

        

        var bg = props.bgColor !== undefined ? props.bgColor : props.background_color;
        if (bg !== undefined) el.style.backgroundColor = bg;
    }

    function resetProps(el) {
        el.style.transform = '';
        el.style.opacity = '';
        el.style.filter = '';
        el.style.color = '';
        el.style.backgroundColor = '';
    }

    

    function apply(screenEl, progress) {
        var p = Math.max(0, Math.min(1, progress));
        var tracked = getTrackedElements(screenEl);
        for (var i = 0; i < tracked.length; i++) {
            var el    = tracked[i];
            var track = getTrack(el);
            if (!track) continue;
            applyProps(el, sampleTrack(track, p));
        }
    }

    function reset(screenEl) {
        var tracked = getTrackedElements(screenEl);
        for (var i = 0; i < tracked.length; i++) resetProps(tracked[i]);
    }


    
    
    function invalidate(el) {
        if (el && _trackCache.has(el)) _trackCache.delete(el);
    }

    window.ImpulseTimeline = {
        hasTimeline: screenHasTimeline,
        apply:       apply,
        reset:       reset,
        invalidate:  invalidate,
        
        _sampleTrack: sampleTrack,
        _getTrack:    getTrack
    };
})();
