Is readable source code available for google's bouncy ball doodle?

I've found a recreation of the bouncy balls here, but the implementation is slighty different. Source code is available though.


Deobfuscated

For the curious, here's a human readable version of the above code. I haven't tested it yet but it should be mostly correct.

Note: to improve readability I've only included stuff in the try block (so it isn't indented twice unnecessarily).

Further Note: Most of the interesting stuff is in the constructor function named Dot (search for 'Dot =').

Code

if (!google.doodle) google.doodle = {};
var interactionDistance = 200,
    mouseX = -200,
    mouseY = -200,
    // position of window on previous update
    lastWindowLeft, lastWindowTop, lastWindowWidth,
    // how much the window has changed since last update
    windowDeltaX = 0, 
    windowDeltaY = 0, 
    windowDeltaW = 0, // width
    updateInterval = 35,
    updateTimerID, dotArray = [],
    dotContainer, isIE, started;
google.doodle.init = function () {
    if (!started && window.location.href.indexOf("#") == -1) {
        started = true;
        if (dotContainer = document.getElementById("hplogo")) {
            if(google.j && google.j.en){
                waitUntilCondition(100, googleInit, function () {
                    return google.rein && google.dstr
                });
            }
            waitUntilCondition(100, registerMouseMoveListener, function () {
                return google.listen
            });
            waitUntilCondition(100, start, function () {
                return google.browser
            })
        }
    }
};

// wait awhile for condition to be true. if it doesn't happen after a
// while, give up.
var waitUntilCondition = function (interval, action, condition) {
        if (condition()) action();

        else if(interval < 200){
            window.setTimeout(function () {
                waitUntilCondition(interval + 1, action, condition)
            }, interval);
        }
    },
    googleInit = function () {
        if (!google.doodle.n) {
            google.doodle.n = true;
            google.rein.push(google.doodle.init);
            google.dstr.push(stop)
        }
    },
    registerMouseMoveListener = function () {
        google.listen(document, "mousemove", mouseMoveListener)
    },
    mouseMoveListener = function (eventObject) {
        // reset the interaction distance
        interactionDistance = 200;

        // figure out where the mouse is
        mouseX = eventObject.clientX - dotContainer.offsetLeft;
        mouseY = eventObject.clientY - dotContainer.offsetTop
    },
    // array of [left, top, width] (cross browser)
    windowInfo = function () {
        return [isIE ? window.screenLeft : window.screenX,
                isIE ? window.screenTop : window.screenY,
                document.body.clientWidth]
    },
    start = function () {

        // check for Internet Ruiner
        isIE = google.browser.engine.IE &&
               google.browser.engine.version != "9.0";

        // make the dots
        dotArray = [createDot(202, 78, 9, "ed9d33"), createDot(348, 83, 9, "d44d61"), createDot(256, 69, 9, "4f7af2"), createDot(214, 59, 9, "ef9a1e"), createDot(265, 36, 9, "4976f3"), createDot(300, 78, 9, "269230"), createDot(294, 59, 9, "1f9e2c"), createDot(45, 88, 9, "1c48dd"), createDot(268, 52, 9, "2a56ea"), createDot(73, 83, 9, "3355d8"), createDot(294, 6, 9, "36b641"), createDot(235, 62, 9, "2e5def"), createDot(353, 42, 8, "d53747"), createDot(336, 52, 8, "eb676f"), createDot(208, 41, 8, "f9b125"), createDot(321, 70, 8, "de3646"), createDot(8, 60, 8, "2a59f0"), createDot(180, 81, 8, "eb9c31"), createDot(146, 65, 8, "c41731"), createDot(145, 49, 8, "d82038"), createDot(246, 34, 8, "5f8af8"), createDot(169, 69, 8, "efa11e"), createDot(273, 99, 8, "2e55e2"), createDot(248, 120, 8, "4167e4"), createDot(294, 41, 8, "0b991a"), createDot(267, 114, 8, "4869e3"), createDot(78, 67, 8, "3059e3"), createDot(294, 23, 8, "10a11d"), createDot(117, 83, 8, "cf4055"), createDot(137, 80, 8, "cd4359"), createDot(14, 71, 8, "2855ea"), createDot(331, 80, 8, "ca273c"), createDot(25, 82, 8, "2650e1"), createDot(233, 46, 8, "4a7bf9"), createDot(73, 13, 8, "3d65e7"), createDot(327, 35, 6, "f47875"), createDot(319, 46, 6, "f36764"), createDot(256, 81, 6, "1d4eeb"), createDot(244, 88, 6, "698bf1"), createDot(194, 32, 6, "fac652"), createDot(97, 56, 6, "ee5257"), createDot(105, 75, 6, "cf2a3f"), createDot(42, 4, 6, "5681f5"), createDot(10, 27, 6, "4577f6"), createDot(166, 55, 6, "f7b326"), createDot(266, 88, 6, "2b58e8"), createDot(178, 34, 6, "facb5e"), createDot(100, 65, 6, "e02e3d"), createDot(343, 32, 6, "f16d6f"), createDot(59, 5, 6, "507bf2"), createDot(27, 9, 6, "5683f7"), createDot(233, 116, 6, "3158e2"), createDot(123, 32, 6, "f0696c"), createDot(6, 38, 6, "3769f6"), createDot(63, 62, 6, "6084ef"), createDot(6, 49, 6, "2a5cf4"), createDot(108, 36, 6, "f4716e"), createDot(169, 43, 6, "f8c247"), createDot(137, 37, 6, "e74653"), createDot(318, 58, 6, "ec4147"), createDot(226, 100, 5, "4876f1"), createDot(101, 46, 5, "ef5c5c"), createDot(226, 108, 5, "2552ea"), createDot(17, 17, 5, "4779f7"), createDot(232, 93, 5, "4b78f1")];

        // get initial window positions
        var windowRect = windowInfo();
        lastWindowLeft = windowRect[0];
        lastWindowTop = windowRect[1];
        lastWindowWidth = windowRect[2];

        // start movin' the dots
        updateDots()
    },

    stop = function () {
        google.unlisten(document, "mousemove", mouseMoveListener);
        updateTimerID && window.clearTimeout(updateTimerID);

        if (dotArray){
            for (var b = 0, dot; dot = dotArray[b++];) dot.remove();   
        }
    },

    /* update the positions of all the dots then set a timer to call this
     * function again in a little while */
    updateDots = function () {
        var i,
            isLongInterval = true;
            windowRect = windowInfo(),
            windowLeft = windowRect[0],
            windowTop = windowRect[1],
            windowWidth = windowRect[2],
            dot;

        // figure how much the window has moved (for the shake effect)
        windowDeltaX = windowLeft - lastWindowLeft;
        windowDeltaY = windowTop - lastWindowTop;
        windowDeltaW = windowWidth - lastWindowWidth;

        lastWindowLeft = windowLeft;
        lastWindowTop = windowTop;
        lastWindowWidth = windowWidth;

        // mouse interaction distance decays when you don't move the mouse
        interactionDistance = Math.max(0, interactionDistance - 2);

        for (i = 0; dot = dotArray[i++];) {
            // move the dots
            dot.update();
            // longer timer interval if all dots are stationary
            isLongInterval = isLongInterval && dot.isStationary;
        }
        updateInterval = isLongInterval ? 250 : 35;

        updateTimerID = window.setTimeout(updateDots, updateInterval)
    },
    createDot = function (x0, y0, radius, color) {
        return new Dot(x0, y0, radius, color)
    },
    Dot = function (x0, y0, radius, color) {
        this.x = this.x0 = x0;
        this.y = this.y0 = y0;
        this.dotRadius = this.baseDotRadius = radius;

        // move us some random amount away from our start position, so we
        // look cool when the page loads
        var b = 100;
        this.xDelta = b * (Math.random() - 0.5);
        this.yDelta = b * (Math.random() - 0.5);

        // how strongly do we move away from the cursor?
        // (between 3 and 100)
        this.repulsion = 3 + Math.random() * 98;
        // how strongly do we try to move toward our rest position? 
        // (between 0.1 and 0.5)
        this.springiness = 0.1 + Math.random() * 0.4;

        // how much is our spring pulling on us right now?
        this.springPull = 0;
        // how unlikely is this dot to move away from the cursor right now?
        // (between 0 and 1)
        this.laziness = 1;

        this.isStationary = false;
        this.particle = document.createElement("div");
        this.particle.className = "particle";
        this.style = this.particle.style;
        color = "#" + color;
        if (isIE) {
            this.particle.innerHTML = ".";
            this.style.fontFamily = "Monospace";
            this.style.color = color;
            this.style.fontSize = "100px";
            this.style.lineHeight = 0;
            this.style.cursor = "default"
        } else {
            this.particle.className += " circle";
            this.style.backgroundColor = color
        }
        dotContainer.appendChild(this.particle);
        this.remove = function () {
            dotContainer.removeChild(this.particle)
        };
        this.update = function () {
            // update position for this frame
            this.x += this.xDelta;
            this.y += this.yDelta;

            // figure out how much to move it next frame

            // base change is based on window movement
            var scaledWindowX = 
                    (windowDeltaX + windowDeltaW) / this.baseDotRadius,
                scaledWindowY = windowDeltaY / this.baseDotRadius;

            this.xDelta = Math.min(50, 
                    Math.max(-50, (this.xDelta + scaledWindowX) * 0.92));
            this.yDelta = Math.min(50, 
                    Math.max(-50, (this.yDelta + scaledWindowY) * 0.92));

                // how far away is the mouse?
            var xMouseDist = mouseX - this.x,
                yMouseDist = mouseY - this.y,
                mouseDistance = 
                    Math.sqrt(xMouseDist * xMouseDist + yMouseDist * yMouseDist),
                // how far away are we from our rest position?
                xDist = this.x0 - this.x,
                yDist = this.y0 - this.y,
                displacement = Math.sqrt(xDist * xDist + yDist * yDist);


            // normalize the mouse's relative position vector
            // (so we react with the same force as long as we're inside the
            // interactionDistance)
            xMouseDist /= mouseDistance;
            yMouseDist /= mouseDistance;

            // are we close to the mouse?
            if (mouseDistance < interactionDistance) { 
                // if so, bounce away from it
                this.xDelta -= xMouseDist * this.repulsion;
                this.yDelta -= yMouseDist * this.repulsion;

                // remove the spring pull when the mouse is close by
                // (approaches 0.005)
                this.springPull += (0.005 - this.springPull) * 0.4;

                // the more the mouse moves, the more responsive close by dots
                // become (laziness goes to zero)
                this.laziness = Math.max(0, this.laziness * 0.9 - 0.01);
                this.xDelta *= 1 - this.laziness;
                this.yDelta *= 1 - this.laziness;
            } else {
                // if not, get more springy and less responsive to the mouse

                // when the mouse isn't close, have the spring pull get slowly
                // closer to the springiness value
                this.springPull += 
                    (this.springiness - this.springPull) * 0.005;

                // when the mouse doesn't move for a while, dots become less
                // responsive (laziness goes to one)
                this.laziness = Math.min(1, this.laziness + 0.03);
            }

            // apply springPull
            this.xDelta += xDist * this.springPull;
            this.yDelta += yDist * this.springPull;

            // figure out how big the dot should be
            this.dotRadius = this.baseDotRadius + displacement / 8;

            // decide if we're moving
            this.isStationary = displacement < 0.3 && 
                                this.xDelta < 0.3 &&
                                this.yDelta < 0.3;

            if (!this.isStationary) {

                // if we aren't on IE, change the dot size
                if (!isIE) {
                    this.style.width =
                    this.style.height = this.dotRadius * 2 + "px";
                } 

                // move it
                this.style.left = this.x - (isIE ? 20 : 0) + "px";
                this.style.top = this.y + "px";
            }
        }
    }

Extras

There's a permanent page with this doodle here.

If I've messed something up, leave me a comment and I'll fix it.