﻿// Much better versions of the lib.js ones which are pants
// These don't just look cool, they actually work properly too
// They are in here because they might not necessarily work quite the same as the lib.js ones
// More will be added
//
// Please do not add yours unless they conform to the design considerations below
//
// Design Considerations:
//
// 1. Cross browser
// 2. Thread-safe
// 3. Compatible with each other
// 4. Able to be cancelled
// 5. Coded very neatly (including proper use of semicolons)
// 6. Useful
//
// I recommend copying one of the existing ones as a starting point, but you will probably need to write a getter and setter first for whatever property you want to animate
//
// - Tom

var TomsEffects = new function () {

    var me = this;



    /* Handy functions */
    this.GetCursorPosition = function (oEvent) {
        oEvent = oEvent || window.event;
        var oCursorPosition = { x: 0, y: 0 };

        if (oEvent.pageX || oEvent.pageY) {
            oCursorPosition.x = oEvent.pageX;
            oCursorPosition.y = oEvent.pageY;
        } else {
            var oDocument = document.documentElement;
            var oBody = document.body;
            oCursorPosition.x = oEvent.clientX + (oDocument.scrollLeft || oBody.scrollLeft) - (oDocument.clientLeft || 0);
            oCursorPosition.y = oEvent.clientY + (oDocument.scrollTop || oBody.scrollTop) - (oDocument.clientTop || 0);
        };

        return oCursorPosition;
    };


    this.GetObjectPosition = function (oObject) {
        var oObjectPosition = { left: 0, top: 0, right: 0, bottom: 0 };
        oObject = f.SafeObject(oObject);

        if (oObject) {
            var oObjectTemp = oObject;

            while (oObjectTemp) {
                oObjectPosition.left += oObjectTemp.offsetLeft;
                oObjectPosition.top += oObjectTemp.offsetTop;

                if (oObjectTemp.offsetParent && oObjectTemp.clientLeft) oObjectPosition.left += oObjectTemp.clientLeft;
                if (oObjectTemp.offsetParent && oObjectTemp.clientTop) oObjectPosition.top += oObjectTemp.clientTop;

                oObjectTemp = oObjectTemp.offsetParent;
            };

            oObjectPosition.right = oObjectPosition.left + oObject.offsetWidth - 1;
            oObjectPosition.bottom = oObjectPosition.top + oObject.offsetHeight - 1;
        };

        oObjectPosition.Overlaps = function (oPosition) {
            return ((this.right >= oPosition.left && this.left <= oPosition.right) || (this.left <= oPosition.right && this.right >= oPosition.left))
                && ((this.bottom >= oPosition.top && this.top <= oPosition.bottom) || (this.top <= oPosition.bottom && this.bottom >= oPosition.top));
        };

        return oObjectPosition;
    };


    this.CursorWithinObject = function (oEvent, oObject) {
        var oCursorPos = this.GetCursorPosition(oEvent);
        var oObjectPos = this.GetObjectPosition(oObject);

        return oCursorPos.x >= oObjectPos.left && oCursorPos.x <= oObjectPos.right && oCursorPos.y >= oObjectPos.top && oCursorPos.y <= oObjectPos.bottom;
    };


    this.IsNull = function (oValue, oReplacementValue) {
        if (oValue != undefined && oValue != null) {
            return oValue;
        } else {
            return oReplacementValue;
        };
    };

    this.IsNotNumber = function (nNumeric, nReplacementNumeric) {
        if (nNumeric != undefined && nNumeric != null && !isNaN(nNumeric)) {
            return nNumeric;
        } else {
            return nReplacementNumeric;
        };
    };

    this.Hex = function (iNumber) {
        var sHexLookup = '0123456789abcdef';

        var sHex = '';
        for (var i = iNumber; i > 0; i = Math.floor(i / 16.0)) {
            sHex = sHexLookup.charAt(i % 16) + sHex;
        };

        return sHex;
    };

    this.Unhex = function (sHex) {
        var sHexLookup = '0123456789abcdef';

        var iNumber = 0;
        for (var i = 0; i < sHex.length; i++) {
            iNumber += Math.pow(16, i) * sHexLookup.indexOf(sHex.charAt(sHex.length - i - 1).toLowerCase());
        };

        return iNumber;
    };

    this.RGBToHex = function (iRed, iGreen, iBlue) {
        return '#' + s.PadWithZeros(this.Hex(Math.round(iRed)), 2) + s.PadWithZeros(this.Hex(Math.round(iGreen)), 2) + s.PadWithZeros(this.Hex(Math.round(iBlue)), 2);
    };

    this.HexToRGB = function (sColor) {
        if (sColor.length == 4) sColor = '#' + sColor.charAt(1) + sColor.charAt(1) + sColor.charAt(2) + sColor.charAt(2) + sColor.charAt(3) + sColor.charAt(3);

        return {
            Red: this.Unhex(sColor.substr(1, 2)),
            Green: this.Unhex(sColor.substr(3, 2)),
            Blue: this.Unhex(sColor.substr(5, 2)),
            ToHex: function () { return me.RGBToHex(this.Red, this.Green, this.Blue); }
        };
    };

    this.ParseRGB = function (sRGBColor) {
        var aRGBValues = sRGBColor.substring(sRGBColor.indexOf('(') + 1, sRGBColor.length - 1).split(',');

        return {
            Red: parseInt(aRGBValues[0]),
            Green: parseInt(aRGBValues[1]),
            Blue: parseInt(aRGBValues[2]),
            ToHex: function () { return me.RGBToHex(this.Red, this.Green, this.Blue); }
        };
    };



    /* Get computed style */
    this.GetComputedStyle = function (oObject) {
        oObject = f.SafeObject(oObject);

        if (oObject.currentStyle != undefined) {
            return oObject.currentStyle;
        } else {
            return document.defaultView.getComputedStyle(oObject, null);
        };
    };



    /* Get / set object opacity */
    this.GetObjectOpacity = function (oObject) {
        oObject = f.SafeObject(oObject);

        var oComputedStyle = me.GetComputedStyle(oObject);
        if (oComputedStyle.opacity) {
            return oComputedStyle.opacity * 100;
        } else if (oObject.filters && oObject.filters.alpha) {
            return oObject.filters.alpha.opacity;
        } else {
            return 100;
        };
    };


    this.SetObjectOpacity = function (oObject, iOpacity) {
        oObject = f.SafeObject(oObject);

        if (iOpacity != undefined && !isNaN(iOpacity)) {
            oObject.style.opacity = iOpacity / 100;

            if (oObject.filters && oObject.filters.alpha) {
                oObject.filters.alpha.opacity = iOpacity;
            } else {
                oObject.style.filter += ' alpha(opacity=' + iOpacity + ')';
            };
        };
    };



    /* Get / set object size */
    this.GetObjectSize = function (oObject) {
        oObject = f.SafeObject(oObject);

        var oObjectSize = {
            OffsetWidth: oObject.offsetWidth,
            OffsetHeight: oObject.offsetHeight,
            Width: 0, Height: 0
        };

        oObjectSize.Width = oObjectSize.OffsetWidth;
        oObjectSize.Height = oObjectSize.OffsetHeight;
        oObjectSize.Width -= (oObject.style.paddingLeft ? oObject.style.paddingLeft : 0) + (oObject.style.paddingRight ? oObject.style.paddingRight : 0);
        oObjectSize.Height -= (oObject.style.paddingTop ? oObject.style.paddingTop : 0) + (oObject.style.paddingBottom ? oObject.style.paddingBottom : 0);
        oObjectSize.Width -= (oObject.style.borderLeftWidth ? oObject.style.borderLeftWidth : 0) + (oObject.style.borderRightWidth ? oObject.style.borderRightWidth : 0);
        oObjectSize.Height -= (oObject.style.borderTopWidth ? oObject.style.borderTopWidth : 0) + (oObject.style.borderBottomWidth ? oObject.style.borderBottomWidth : 0);

        return oObjectSize;
    };


    this.SetObjectSize = function (oObject, oObjectSize) {
        oObject = f.SafeObject(oObject);

        if (!isNaN(oObjectSize.Width)) oObject.style.width = oObjectSize.Width + 'px';
        if (!isNaN(oObjectSize.Height)) oObject.style.height = oObjectSize.Height + 'px';
    };



    /* Get / set object offset */
    this.GetObjectOffset = function (oObject) {
        oObject = f.SafeObject(oObject);

        var oComputedStyle = me.GetComputedStyle(oObject);
        var oObjectOffset = {
            Top: parseInt(oComputedStyle.top),
            Bottom: parseInt(oComputedStyle.bottom),
            Left: parseInt(oComputedStyle.left),
            Right: parseInt(oComputedStyle.right)
        };

        oObjectOffset.Top = !isNaN(oObjectOffset.Top) ? oObjectOffset.Top : 0;
        oObjectOffset.Bottom = !isNaN(oObjectOffset.Bottom) ? oObjectOffset.Bottom : 0;
        oObjectOffset.Left = !isNaN(oObjectOffset.Left) ? oObjectOffset.Left : 0;
        oObjectOffset.Right = !isNaN(oObjectOffset.Right) ? oObjectOffset.Right : 0;

        return oObjectOffset;
    };


    this.SetObjectOffset = function (oObject, oObjectOffset) {
        oObject = f.SafeObject(oObject);

        if (!isNaN(oObjectOffset.Top)) oObject.style.top = oObjectOffset.Top + 'px';
        if (!isNaN(oObjectOffset.Bottom)) oObject.style.bottom = oObjectOffset.Bottom + 'px';
        if (!isNaN(oObjectOffset.Left)) oObject.style.left = oObjectOffset.Left + 'px';
        if (!isNaN(oObjectOffset.Right)) oObject.style.right = oObjectOffset.Right + 'px';
    };



    /* Get / set object background color */
    this.GetObjectBgColor = function (oObject) {
        oObject = f.SafeObject(oObject);

        var oComputedStyle = me.GetComputedStyle(oObject);
        var sBackgroundColor = me.IsNull(oComputedStyle.backgroundColor, '#fff');

        if (sBackgroundColor.indexOf('#') == 0) {
            return me.HexToRGB(sBackgroundColor);
        } else {
            return me.ParseRGB(sBackgroundColor);
        };
    };


    this.SetObjectBgColor = function (oObject, oObjectBgColor) {
        oObject = f.SafeObject(oObject);

        if (oObjectBgColor.ToHex) {
            oObject.style.backgroundColor = oObjectBgColor.ToHex();
        } else {
            oObject.style.backgroundColor = oObjectBgColor;
        };
    };



    /* Blending Functions */
    this.BlendingFunctions = new function () {

        this.Linear = function (n) { return n; };
        this.Quadratic = function (n) { return Math.pow(n, 2); };
        this.Cubic = function (n) { return Math.pow(n, 3); };
        this.Exponential = function (n) { return (Math.exp(n) - 1) / (Math.E - 1); };
        this.Sine = function (n) { return Math.sin(Math.PI / 2 * n); };
        this.Cosine = function (n) { return 1 / 2 - Math.cos(Math.PI * n) / 2; };

        // use these to combine any two of the above (or only hand in one parameter to combine two of the same style)
        this.Multiply = function (oFirstFunction, oSecondFunction) {
            oSecondFunction = me.IsNull(oSecondFunction, oFirstFunction);
            return function (n) { return oFirstFunction(n) * oSecondFunction(n); };
        };
        this.Average = function (oFirstFunction, oSecondFunction) {
            oSecondFunction = me.IsNull(oSecondFunction, oFirstFunction);
            return function (n) { return (oFirstFunction(n) + oSecondFunction(n)) / 2; };
        };
        this.Sequence = function (oFirstFunction, oSecondFunction) {
            oSecondFunction = me.IsNull(oSecondFunction, oFirstFunction);
            return function (n) { return n < 1 / 2 ? oFirstFunction(n * 2) / 2 : oSecondFunction((1 - n) * 2) / 2; };
        };
        this.Blend = function (oFirstFunction, oSecondFunction) {
            oSecondFunction = me.IsNull(oSecondFunction, oFirstFunction);
            return function (n) { return n < 1 / 2 ? oFirstFunction(n * 2) / 2 : 1 - oSecondFunction((1 - n) * 2) / 2; };
        };

    };


    /* Animate */
    this.Animate = function (oAnimationResult, nSpeed, bTreatSpeedAsTotalTime, iStep, oCallbackFunction, oDoneCallbackFunction) {

        // set default parameters
        nSpeed = me.IsNull(nSpeed, 100); // normally, if nSpeed = 100, then it will take 1 second to fade from 0 to 100.
        bTreatSpeedAsTotalTime = me.IsNull(bTreatSpeedAsTotalTime, false); // if true then nSpeed is the total time in milliseconds instead
        iStep = me.IsNull(iStep, 10); // the step of the animation (to small and the browser might struggle, to big and the animation will be choppy)


        //get the start and target times
        var iTotalTime = bTreatSpeedAsTotalTime ? nSpeed : oAnimationResult.Details.Difference * 1000 / nSpeed;
        var iStartTime = (new Date()).getTime();
        var iTargetTime = iStartTime + iTotalTime;


        // create the animation loop
        var iCurrentTime;

        var oAnimationLoop = function () {
            iCurrentTime = (new Date()).getTime();
            iCurrentTime = iCurrentTime < iTargetTime ? iCurrentTime : iTargetTime;

            oAnimationResult.AnimationFunction((iCurrentTime - iStartTime) / (iTargetTime - iStartTime));

            if (iCurrentTime === iTargetTime) clearInterval(oAnimationResult.Details.IntervalID);

            if (me.IsNull(oCallbackFunction)) oCallbackFunction();
            if (me.IsNull(oDoneCallbackFunction) && iCurrentTime === iTargetTime) oDoneCallbackFunction();
        };


        // run it
        oAnimationResult.Details.IntervalID = setInterval(oAnimationLoop, iStep);

    };


    /* Animate Multiple */
    this.AnimateMultiple = function (nSpeed, bTreatSpeedAsTotalTime, iStep, oCallbackFunction, oDoneCallbackFunction) {

        // set default parameters and get the other arguments
        nSpeed = me.IsNull(nSpeed, 100); // normally, if nSpeed = 100, then it will take 1 second to fade from 0 to 100.
        bTreatSpeedAsTotalTime = me.IsNull(bTreatSpeedAsTotalTime, true); // if true then nSpeed is the total time in milliseconds instead
        iStep = me.IsNull(iStep, 10); // the step of the animation (to small and the browser might struggle, to big and the animation will be choppy)

        var oArguments = arguments;
        var iTotalFixedArguments = 5;


        //get the start and target times
        var iMaxDifference = 0;

        for (var i = iTotalFixedArguments; i < oArguments.length; i++) {
            if (oArguments[i].Details.Difference > iMaxDifference) iMaxDifference = oArguments[i].Details.Difference;
        };

        var iTotalTime = bTreatSpeedAsTotalTime ? nSpeed : iMaxDifference * 1000 / nSpeed;
        var iStartTime = (new Date()).getTime();
        var iTargetTime = iStartTime + iTotalTime;


        // create the animation loop
        var iIntervalID;
        var iCurrentTime;

        var oAnimationLoop = function () {
            iCurrentTime = (new Date()).getTime();
            iCurrentTime = iCurrentTime < iTargetTime ? iCurrentTime : iTargetTime;

            for (var i = iTotalFixedArguments; i < oArguments.length; i++) {
                oArguments[i].AnimationFunction((iCurrentTime - iStartTime) / (iTargetTime - iStartTime));
            };

            if (iCurrentTime === iTargetTime) clearInterval(iIntervalID);

            if (me.IsNull(oCallbackFunction)) oCallbackFunction();
            if (me.IsNull(oDoneCallbackFunction) && iCurrentTime === iTargetTime) oDoneCallbackFunction();
        };


        // run it
        iIntervalID = setInterval(oAnimationLoop, iStep);


        // set the interval ID on all the details objects
        for (var i = iTotalFixedArguments; i < oArguments.length; i++) {
            oArguments[i].Details.IntervalID = iIntervalID;
        };
    };



    /* Fade */
    this.Fade = function (oObject, iFadeTo, oBlendingFunction) {

        // safety check parameters
        oObject = f.SafeObject(oObject);
        iFadeTo = me.IsNull(iFadeTo);
        oBlendingFunction = me.IsNull(oBlendingFunction, me.BlendingFunctions.Linear);


        // set up the details and ensure no other animation is currently controlling them
        if (oObject._TomsEffects == undefined) oObject._TomsEffects = {};
        if (oObject._TomsEffects.OpacityDetails) oObject._TomsEffects.OpacityDetails.Stop();

        oObject._TomsEffects.OpacityDetails = {
            Opacity: me.GetObjectOpacity(oObject),
            Difference: 0,
            IntervalID: 0,
            Stop: function () { if (this.IntervalID != 0) { clearInterval(this.IntervalID); this.IntervalID = 0; }; }
        };
        var oOpacityDetails = oObject._TomsEffects.OpacityDetails;


        // store the original values and work out the maximum difference in value
        var iOriginalOpacity = oOpacityDetails.Opacity;
        oOpacityDetails.Difference = me.IsNotNumber(Math.abs(iFadeTo - iOriginalOpacity), 0);


        // define the function that does the animation
        var nMultiplier;

        var oFadeFunction = function (nFractionDone) {
            nMultiplier = me.IsNotNumber(oBlendingFunction(nFractionDone), 1);

            oOpacityDetails.Opacity = iOriginalOpacity + (iFadeTo - iOriginalOpacity) * nMultiplier;
            me.SetObjectOpacity(oObject, oOpacityDetails.Opacity);
        };


        // return
        return { Details: oOpacityDetails, AnimationFunction: oFadeFunction };
    };



    /* Resize */
    this.Resize = function (oObject, iResizeToWidth, iResizeToHeight, oBlendingFunction) {

        // safety check parameters
        oObject = f.SafeObject(oObject);
        iResizeToWidth = me.IsNull(iResizeToWidth);
        iResizeToHeight = me.IsNull(iResizeToHeight);
        oBlendingFunction = me.IsNull(oBlendingFunction, me.BlendingFunctions.Cosine);


        // set up the details and ensure no other animation is currently controlling them
        if (oObject._TomsEffects == undefined) oObject._TomsEffects = {};
        if (oObject._TomsEffects.SizeDetails) oObject._TomsEffects.SizeDetails.Stop();

        var oObjectSize = me.GetObjectSize(oObject);
        oObject._TomsEffects.SizeDetails = {
            Width: oObjectSize.Width,
            Height: oObjectSize.Height,
            Difference: 0,
            IntervalID: 0,
            Stop: function () { if (this.IntervalID != 0) { clearInterval(this.IntervalID); this.Interval = 0; }; }
        };
        var oSizeDetails = oObject._TomsEffects.SizeDetails;


        // store the original values and work out the maximum difference in value
        var iOriginalWidth = oSizeDetails.Width;
        var iOriginalHeight = oSizeDetails.Height;
        oSizeDetails.Difference = Math.max(me.IsNotNumber(Math.abs(iResizeToWidth - iOriginalWidth), 0), me.IsNotNumber(Math.abs(iResizeToHeight - iOriginalHeight), 0));


        // define the function that does the animation
        var nMultiplier;

        var oResizeFunction = function (nFractionDone) {
            nMultiplier = me.IsNotNumber(oBlendingFunction(nFractionDone), 1);

            oSizeDetails.Width = iOriginalWidth + (iResizeToWidth - iOriginalWidth) * nMultiplier;
            oSizeDetails.Height = iOriginalHeight + (iResizeToHeight - iOriginalHeight) * nMultiplier;
            me.SetObjectSize(oObject, oSizeDetails);
        };


        // return
        return { Details: oSizeDetails, AnimationFunction: oResizeFunction };
    };



    /* Move */
    this.Move = function (oObject, iTargetOffsetTop, iTargetOffsetBottom, iTargetOffsetLeft, iTargetOffsetRight, oBlendingFunction) {

        // safety check parameters
        oObject = f.SafeObject(oObject);
        iTargetOffsetTop = me.IsNull(iTargetOffsetTop);
        iTargetOffsetBottom = me.IsNull(iTargetOffsetBottom);
        iTargetOffsetLeft = me.IsNull(iTargetOffsetLeft);
        iTargetOffsetRight = me.IsNull(iTargetOffsetRight);
        oBlendingFunction = me.IsNull(oBlendingFunction, me.BlendingFunctions.Cosine);


        // set up the details and ensure no other animation is currently controlling them
        if (oObject._TomsEffects == undefined) oObject._TomsEffects = {};
        if (oObject._TomsEffects.ScrollDetails) oObject._TomsEffects.OffsetDetails.Stop();

        var oObjectOffset = me.GetObjectOffset(oObject);
        oObject._TomsEffects.OffsetDetails = {
            Top: oObjectOffset.Top,
            Bottom: oObjectOffset.Bottom,
            Left: oObjectOffset.Left,
            Right: oObjectOffset.Right,
            Difference: 0,
            IntervalID: 0,
            Stop: function () { if (this.IntervalID != 0) { clearInterval(this.IntervalID); this.Interval = 0; }; }
        };
        var oOffsetDetails = oObject._TomsEffects.OffsetDetails;


        // store the original values and work out the maximum difference in value
        var iOriginalOffsetTop = oOffsetDetails.Top;
        var iOriginalOffsetBottom = oOffsetDetails.Bottom;
        var iOriginalOffsetLeft = oOffsetDetails.Left;
        var iOriginalOffsetRight = oOffsetDetails.Right;
        oOffsetDetails.Difference = Math.max(me.IsNotNumber(Math.abs(iTargetOffsetTop - iOriginalOffsetTop), 0), me.IsNotNumber(Math.abs(iTargetOffsetBottom - iOriginalOffsetBottom), 0),
                me.IsNotNumber(Math.abs(iTargetOffsetLeft - iOriginalOffsetLeft), 0), me.IsNotNumber(Math.abs(iTargetOffsetRight - iOriginalOffsetRight), 0));


        // define the function that does the animation
        var nMultiplier;

        var oMoveFunction = function (nFractionDone) {
            nMultiplier = me.IsNotNumber(oBlendingFunction(nFractionDone), 1);

            oOffsetDetails.Top = iOriginalOffsetTop + (iTargetOffsetTop - iOriginalOffsetTop) * nMultiplier;
            oOffsetDetails.Bottom = iOriginalOffsetBottom + (iTargetOffsetBottom - iOriginalOffsetBottom) * nMultiplier;
            oOffsetDetails.Left = iOriginalOffsetLeft + (iTargetOffsetLeft - iOriginalOffsetLeft) * nMultiplier;
            oOffsetDetails.Right = iOriginalOffsetRight + (iTargetOffsetRight - iOriginalOffsetRight) * nMultiplier;
            me.SetObjectOffset(oObject, oOffsetDetails);
        };


        // return
        return { Details: oOffsetDetails, AnimationFunction: oMoveFunction };
    };



    /* Background Recolor */
    this.BackgroundRecolor = function (oObject, sTargetBgColor, oBlendingFunction) {

        // safety check parameters
        oObject = f.SafeObject(oObject);
        sTargetBgColor = me.IsNull(sTargetBgColor);
        oBlendingFunction = me.IsNull(oBlendingFunction, me.BlendingFunctions.Cosine);


        // set up the details and ensure no other animation is currently controlling them
        if (oObject._TomsEffects == undefined) oObject._TomsEffects = {};
        if (oObject._TomsEffects.BackgroundColorDetails) oObject._TomsEffects.BackgroundColorDetails.Stop();

        var oObjectBgColor = me.GetObjectBgColor(oObject);

        oObject._TomsEffects.BackgroundColorDetails = {
            BgColor: oObjectBgColor,
            IntervalID: 0,
            Difference: 0,
            Stop: function () { if (this.IntervalID != 0) { clearInterval(this.IntervalID); this.Interval = 0; }; }
        };
        var oBackgroundColorDetails = oObject._TomsEffects.BackgroundColorDetails;


        // store the original values and work out the maximum difference in value
        var iOriginalRed = oBackgroundColorDetails.BgColor.Red;
        var iOriginalGreen = oBackgroundColorDetails.BgColor.Green;
        var iOriginalBlue = oBackgroundColorDetails.BgColor.Blue;
        var oTargetBgColor = me.HexToRGB(me.IsNull(sTargetBgColor, '#fff'));
        oBackgroundColorDetails.Difference = Math.max(me.IsNotNumber(Math.abs(oTargetBgColor.Red - iOriginalRed), 0), me.IsNotNumber(Math.abs(oTargetBgColor.Green - iOriginalGreen), 0),
                me.IsNotNumber(Math.abs(oTargetBgColor.Blue - iOriginalBlue), 0));


        // define the function that does the animation
        var nMultiplier;

        var oRecolorFunction = function (nFractionDone) {
            nMultiplier = me.IsNotNumber(oBlendingFunction(nFractionDone), 1);

            oBackgroundColorDetails.BgColor.Red = iOriginalRed + (oTargetBgColor.Red - iOriginalRed) * nMultiplier;
            oBackgroundColorDetails.BgColor.Green = iOriginalGreen + (oTargetBgColor.Green - iOriginalGreen) * nMultiplier;
            oBackgroundColorDetails.BgColor.Blue = iOriginalBlue + (oTargetBgColor.Blue - iOriginalBlue) * nMultiplier;
            me.SetObjectBgColor(oObject, oBackgroundColorDetails.BgColor);
        };


        // return
        return { Details: oBackgroundColorDetails, AnimationFunction: oRecolorFunction };
    };

};
