
// TRICKY: We have to declare this here using "var", rather than later using "window"
// (otherwise, Internet Explorer will treat it as a separate variable from the one
// declared within the page itself using "var").
var ModalListeners = ModalListeners || [];

/*  =Spinner
-----------------------------------------------
Based on SVG/VML spinner by Dmitry Baranovskiy
(http://raphaeljs.com/spin-spin-spin.html)
----------------------------------------------- */
var Spinner = function(element) {
  if (typeof(element) == "String") element = document.getElementById(element);
  if (!element) return;

  // Attributes
    var c = {
      innerRadius : 35,
      outerRadius : 60,
      spokeCount  : 12,
      spokeWidth : 12,
      color : "#000000",
      opacity: "0.5",
      size: null  // Height x Width (in pixels). For example, a size of 50 will generate a 50px by 50px spinner.
    };
    var defaultSize = Math.ceil((c.outerRadius * 2) + (c.spokeWidth * 2));

    function extend(destination, source) {
      for (var property in source) destination[property] = source[property];
      return destination;
    }
    extend(c, arguments[1] || {});

    var scale = 1;
    var spokes = [];
    var spokeOpacities = [];
    var interval;

    // If a target size was specified, scale the image to match it.
    if (c.size != null) {
      scale = c.size / defaultSize;
    }

    var isMSIE = /*@cc_on!@*/false;
    if (isMSIE) {
      var coordsize = 1000;
      var shapesize = Math.round(1000 * scale);
      c.spokeWidth = Math.round(c.spokeWidth * scale);
    }

  // Methods
    function initialize() {
      render();
      start();
    }
    function render() {
      var beta = 2 * Math.PI / c.spokeCount;
      var cx = c.outerRadius + c.spokeWidth;
      var cy = c.outerRadius + c.spokeWidth;
      var containerSize = Math.ceil(((c.outerRadius * 2) + (c.spokeWidth * 2)) * scale);
      if (isMSIE) {
        document.createStyleSheet().addRule(".cevml", "behavior:url(#default#VML)");
        var div = document.createElement("div");
        div.style.position = "absolute";
        div.style.clip = "rect(0px " + containerSize + "px " + containerSize + "px 0px)";
        div.style.width = containerSize + "px";
        div.style.height = containerSize + "px";
        div.style.overflow = "hidden";
        element.appendChild(div);
      } else {
        var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        svg.setAttribute("width", containerSize);
        svg.setAttribute("height", containerSize);
        element.appendChild(svg);
        var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
        g.setAttribute("transform", "scale(" + scale + ")");
        svg.appendChild(g);
      }
      for (var index = 0; index < c.spokeCount; index++) {
        var alpha = beta * index - Math.PI / 2;
        var cos = Math.cos(alpha);
        var sin = Math.sin(alpha);
        var spokeCommand = {
          m: {
            x: cx + c.innerRadius * cos,
            y: cy + c.innerRadius * sin
            },
          l: {
            x: cx + c.outerRadius * cos,
            y: cy + c.outerRadius * sin
          }
        };
        var spoke = createSpoke(spokeCommand, c.spokeWidth, c.color, c.opacity);
        spokeOpacities[index] = 1 / c.spokeCount * index;
        if (isMSIE) {
          div.appendChild(spoke.group);
        } else {
          g.appendChild(spoke);
        }
        spokes.push(spoke);
      }
    };
    function createSpoke(command, width, color, opacity) {
      if (isMSIE) {
        var group = createIENode("group");
        group.style.position = "absolute";
        group.style.width = "1000px";
        group.style.height = "1000px";
        group.style.top = "0";
        group.style.left = "0";
        group.setAttribute("coordsize", String(coordsize) + "," + String(coordsize));
        var shape = createIENode("shape");
        shape.style.width = shapesize + "px";
        shape.style.height = shapesize + "px";
        group.setAttribute("coordsize", String(coordsize) + "," + String(coordsize));
        shape.setAttribute("filled", "f");
        shape.setAttribute("stroked", "t");
        shape.setAttribute("strokecolor", color);
        shape.setAttribute("strokeweight", String(c.spokeWidth * 0.75) + "pt");
        var d = "m" + Math.round(command.m.x) +  "," + Math.round(command.m.y) + " l" + Math.round(command.l.x) + "," + Math.round(command.l.y) + " e";
        shape.setAttribute("path", d);
        group.appendChild(shape);
        var fill = createIENode("fill");
        shape.appendChild(fill);
        var stroke = createIENode("stroke");
        stroke.setAttribute("miterlimit", "8");
        stroke.setAttribute("endcap", "round");
        shape.appendChild(stroke);
        return {
          group: group,
          shape: shape,
          fill: fill,
          stroke: stroke
        };
      } else {
        var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
        path.setAttribute("fill", "none");
        path.setAttribute("stroke", color);
        path.setAttribute("stroke-width", width);
        path.setAttribute("stroke-opacity", opacity);
        path.setAttribute("stroke-linecap", "round");
        var d = "M" + command.m.x +  "," + command.m.y + "L" + command.l.x + "," + command.l.y;
        path.setAttribute("d", d);
        return path;
      }
    };
    function spin() {
      spokeOpacities.unshift(spokeOpacities.pop());
      for (var index = 0; index < spokes.length; index++) {
        if (isMSIE) {
          spokes[index].fill.opacity = spokeOpacities[index];
          spokes[index].stroke.opacity = spokeOpacities[index];
        } else {
          spokes[index].setAttribute("opacity", spokeOpacities[index]);
        }
      }
    };
    if (isMSIE) {
      try {
        if (!document.namespaces.cevml) document.namespaces.add("cevml", "urn:schemas-microsoft-com:vml");
        function createIENode(tagName) {
          return document.createElement("<cevml:" + tagName + " class='cevml'>");
        };
      } catch(e) {
        function createIENode(tagName) {
          return document.createElement("<" + tagName + " xmlns='urn:schemas-microsoft.com:vml' class='cevml'>");
        };
      }
    }
    function start() {
      spin();
      interval = setInterval(spin, Math.round(1000 / c.spokeCount));
    }
    function stop() {
      if (interval != null) {
        clearInterval(interval);
        interval = null;
      }
    }

    initialize();

  // Methods (Public)
    return {
      start: start,
      stop: stop
    };
};


(function() { // BEGIN: Sandbox

var isMSIE = /*@cc_on!@*/false;
var version = -1;
if (isMSIE) version = parseFloat(navigator.appVersion.split("MSIE")[1]) || -1;

var mainCalled = false;

function main() {
  if (mainCalled == true) return;
  mainCalled = true;

  // Create a pseudo-console, if a real one doesn't exist
  // (to prevent console statements from halting this script).
  var console = window.console || {};
  if (!console.log)   console.log   = function() {};
  if (!console.dir)   console.dir   = function() {};
  if (!console.error) console.error = function() {};


  (function() {
    var tries = 0;
    function waitForHTML() {
      var html = document.getElementsByTagName("html");
      if (html == null || html.length <= 0) {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForHTML, 100);
        }
      } else {
        tries = 0;
        html[0].className = html[0].className + " scriptable";
      }
    };
    waitForHTML();
  })();


  /* =Utilities
  ----------------------------------------------- */
  function getArray(list) {
    var a = [];
    for (var index = 0; index < list.length; index++) {
      a.push(list[index]);
    }
    return a;
  };
  function addListener(element, eventName, handler, useCapture) {
    if (element.addEventListener) {
      element.addEventListener(eventName, handler, useCapture);
    } else {
      element.attachEvent("on" + eventName, handler);
    }
  };
  function preventDefault(e) {
    if (e.preventDefault) e.preventDefault();
    e.returnValue = false; // For IE
  };
  function getOneElementByClassName(contextElement, className) {

    // getElementsByClassName
    if (contextElement.getElementsByClassName) {
      var elements = contextElement.getElementsByClassName(className);
      return elements[0];
    }

    // For browsers that don't suport getElementsByClassName
    var elements = contextElement.getElementsByTagName("*");
    var length = elements.length;
    for (var index = 0; index < length; index++) {
      if (hasClassName(elements[index], className)) {
        return elements[index];
      }
    }
    return;
  };
  function getAncestor(contextElement, tagName) {
    tagName = String(tagName).toLowerCase();
    var ancestor = contextElement.parentNode;
    while(ancestor && String(ancestor.nodeName).toLowerCase() != tagName) {
      ancestor = ancestor.parentNode;
    }
    return ancestor;
  };
  function getAllElementsByClassName(contextElement, className) {

    // getElementsByClassName
    if (contextElement.getElementsByClassName) {
      return getArray(contextElement.getElementsByClassName(className));
    }

    // For browsers that don't suport getElementsByClassName
    var elements = contextElement.getElementsByTagName("*");
    var length = elements.length;
    var list = [];
    for (var index = 0; index < length; index++) {
      if (hasClassName(elements[index], className)) {
        list.push(elements[index]);
      }
    }
    return list;
  }
  function getAbsolutePosition(target) {
    var position = {
      x: 0,
      y: 0
    };
    do {
      position.x += target.offsetLeft;
      position.y += target.offsetTop;
    } while (target = target.offsetParent);
    return position;
  };

  // KUDOS: Prototype JS (http://www.prototypejs.org/)
  function addClassName(element, className) {
    if (hasClassName(element, className)) return;
    var existingClassName = (element.className == null || element.className == "") ? "" : element.className + " ";
    element.className = existingClassName + className;
  };
  function removeClassName(element, className) {
    element.className = strip(element.className.replace(
      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " "));
  };
  function hasClassName(element, className) {
    var elementClassName = element.className;
    return (elementClassName.length > 0 && (elementClassName == className ||
      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
  };
  function strip(str) {
    return str.replace(/^\s+/, "").replace(/\s+$/, "");
  };

  // KUDOS: http://jehiah.cz/archive/firing-javascript-events-properly
  function fireEvent(element,event){
    if (document.createEventObject) {
      // dispatch for IE
      var evt = document.createEventObject();
      return element.fireEvent("on" + event ,evt);
    } else {
      // dispatch for firefox + others
      var evt = document.createEvent("HTMLEvents");
      evt.initEvent(event, true, true ); // event type,bubbling,cancelable
      return !element.dispatchEvent(evt);
    }
  };

  // KUDOS: http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
  function getViewportDimensions() {
    var width  = 0;
    var height = 0;

    try {
      // Gecko, WebKit & Opera
      if (window.innerWidth) {
        width  = window.innerWidth;
        height = window.innerHeight;
      } else {

        // IE
        if (document.documentElement &&
          document.documentElement.clientWidth &&
          document.documentElement.clientWidth > 0) {
          width  = document.documentElement.clientWidth;
          height = document.documentElement.clientHeight;

        // IE (Quirks-mode)
        } else {
          width  = document.body.clientWidth;
          height = document.body.clientHeight;
        }
      }
    } catch(e) {}

    return {
      width : width,
      height: height
    };
  };

  function getDimensions() {
    var width  = 0;
    var height = 0;

    try {
      var pageDimensions = {
        width : document.body.offsetWidth,
        height: document.body.offsetHeight
      };

      // Special case, for IE in quirksmode
      if (document.body.scrollHeight > pageDimensions.height) {
        pageDimensions = {
          width : document.body.scrollWidth,
          height: document.body.scrollHeight
        };
      }

      var viewportDimensions = getViewportDimensions();

      width  = (pageDimensions.width  > viewportDimensions.width  ? pageDimensions.width  : viewportDimensions.width);
      height = (pageDimensions.height > viewportDimensions.height ? pageDimensions.height : viewportDimensions.height);
    } catch(e) {}

    return {
      width : width,
      height: height
    };
  };
  function getScrollOffsets() {
    var top  = 0;
    var left = 0;

    try {
      if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
        top  = document.body.scrollTop;
        left = document.body.scrollLeft;
      }

      // Special Case, for IE6 in standards compliant mode
      if (document.documentElement.scrollTop > top) {
        top  = document.documentElement.scrollTop;
        left = document.documentElement.scrollLeft;
      }
    } catch(e) {}

    return {
      top : top,
      left: left
    };
  };


  /* =Cookie
  -----------------------------------------------
  KUDOS: http://www.red91.com/2008/02/04/cookies-persistence-javascript
  ----------------------------------------------- */
  var Cookie = {
    set: function(name, value, daysToExpire) {
      if (daysToExpire == null || daysToExpire == undefined) {
        daysToExpire = 365 * 2;
      }
      var expire = '';
      if (daysToExpire != undefined) {
        var d = new Date();
        d.setTime(d.getTime() + (86400000 * parseFloat(daysToExpire)));
        expire = '; expires=' + d.toGMTString();
      }
      return (document.cookie = escape(name) + '=' + escape(value || '') + expire + '; path=/');
    },
    get: function(name) {
      var cookie = document.cookie.match(new RegExp('(^|;)\\s*' + escape(name) + '=([^;\\s]*)'));
      return (cookie ? unescape(cookie[2]) : null);
    },
    erase: function(name) {
      var cookie = Cookie.get(name) || true;
      Cookie.set(name, '', -1);
      return cookie;
    },
    accept: function() {
      if (typeof navigator.cookieEnabled == 'boolean') {
        return navigator.cookieEnabled;
      }
      Cookie.set('_test', '1');
      return (Cookie.erase('_test') === '1');
    }
  };

  var EventDispatcher = function() {
    var listenerStack = null;
    function addListener(eventType, eventHandler) {
      if (listenerStack == null) {
        listenerStack = [];
      }
      var listener = {
        type: eventType,
        handler: eventHandler
      };
      listenerStack.push(listener);
      return listener;
    };
    function dispatchEvent(eventType) {
      if (listenerStack != null) {
        for (var index = 0; index < listenerStack.length; index++) {
          var nextListener = listenerStack[index];
          if (nextListener.type == eventType) {
            nextListener.handler({ type: eventType, target:this });
          }
        }
      }
    };

    return {
      addListener: addListener,
      dispatchEvent: dispatchEvent
    };
  };


  /* =ValidateForm
  ----------------------------------------------- */
  var ValidateForm = function(element) {
    if (!element) return;

    var fields = [];
    var errorMessage;
    var buttons;
    var submitButtonContainer;
    var isMemberForm = (element.id == "member_form");
    var isProfileEditPage = (document.body.id == "profile-edit-page");

    function initialize() {

      var labels = element.getElementsByTagName("label");
      buttons = element.getElementsByTagName("button");

      for (var index = 0; index < labels.length; index++) {
        var label;
        var field;

        // TBD: It might be more reliable to add a required class name to
        // the label, instead of relying on the presence of the "*" indicator.
        required = (labels[index].innerHTML.indexOf("*") >= 0);
        if (!required) continue;
        label = getLabel(labels[index]);
        field = getField(labels[index]);

        if (!label || !field) continue;
        fields.push({
          container: labels[index],
          field: field,
          label: label,
          email: (String(label.toLowerCase()).indexOf("email") >= 0)
        });
      }
      addListener(element, "submit", onSubmit);
    };

    function onSubmit(e) {
      var emptyFields = [];

      /*
      var passwordChanged = false;

      if (isMemberForm && isProfileEditPage) {
        // If one of the password fields has been filled out.
        for (var index = 0; index < fields.length; index++) {
          if (fields[index].field.type == "password" &&
              fields[index].field.value != "" &&
              fields[index].field.value != null) {
            passwordChanged = true;
          }
        }
      }
      */

      for (var index = 0; index < fields.length; index++) {
        var fieldHasValue = fields[index].field.value != "" && fields[index].field.value != null;
        var fieldIsValid = !fields[index].email || isValidEmail(fields[index].field.value);

        // The field is always relevant, unless we're on the profile edit page.
        // In that case, the field is only relevant if it's in the section of
        // the form that the user made a change to. This is either the password
        // section, or the information section.
        /*
        var fieldIsRelevant = !isMemberForm || !isProfileEditPage ||
          (!passwordChanged && fields[index].field.type != "password") ||
          (passwordChanged && fields[index].field.type == "password");
        */

        if (!fieldHasValue || !fieldIsValid) {
          emptyFields.push(fields[index]);
          addClassName(fields[index].container, "error");
        } else {
          removeClassName(fields[index].container, "error");
        }
      }

      if (errorMessage) {
        errorMessage.parentNode.removeChild(errorMessage);
        errorMessage = null;
      }
      if (emptyFields.length <= 0) return;

      errorMessage = document.createElement("div");
      errorMessage.className = "error-message";

      // This makes the offsetLeft calculation work properly.
      //element.style.position = "relative";
      //errorMessage.style.marginLeft = submitButtonContainer.offsetLeft + "px";

      if (!buttons || !buttons.length || buttons.length <= 0) return;

      /*
      if (!isMemberForm || !isProfileEditPage || passwordChanged) {
        submitButtonContainer = getAncestor(buttons[buttons.length - 1], "p");
        if (submitButtonContainer == null) {
          submitButtonContainer = getAncestor(buttons[buttons.length - 1], "ul");
        }
      } else {
      */
      submitButtonContainer = getAncestor(buttons[buttons.length - 1], "p");
      if (submitButtonContainer == null) {
        submitButtonContainer = getAncestor(buttons[buttons.length - 1], "ul");
      }
      submitButtonContainer.parentNode.insertBefore(errorMessage, submitButtonContainer);

      var messageText = document.createElement("p");
      messageText.appendChild(document.createTextNode("One or more required fields is empty or contains an invalid value:"));
      errorMessage.appendChild(messageText);

      var errorMessageList = document.createElement("ul");
      errorMessage.appendChild(errorMessageList);

      for (var index = 0; index < emptyFields.length; index++) {
        var li = document.createElement("li");
        errorMessageList.appendChild(li);
        var span = document.createElement("span");
        li.appendChild(span);
        span.innerHTML = emptyFields[index].label;
      }

      preventDefault(e);
    }

    function getLabel(element) {
      try {
        var labelContainer = element.getElementsByTagName("span")[0];
        var candidates = labelContainer.childNodes;
        for (var index = 0; index < candidates.length; index++) {
          // If it's a text node, and it has length, assume it's the label.
          if (candidates[index].nodeType == 3 && candidates[index].length > 1) {
            return candidates[index].nodeValue;
          }
        }
      } catch(e) {
        console.error(e);
      }
    };

    function getField(element) {
      var candidates = element.childNodes;
      for (var index = 0; index < candidates.length; index++) {
        if (String(candidates[index].nodeName).toLowerCase() == "textarea" ||
            (String(candidates[index].nodeName).toLowerCase() == "input" &&
            ( candidates[index].type == "text" ||
              candidates[index].type == "email" ||
              candidates[index].type == "username" ||
              candidates[index].type == "password"))) {
          return candidates[index];
        }
      }
    };

    function isValidEmail(value) {
        /* SOLSPACE HACK 2011-06-20 */
        var emails  = (value.indexOf(',') != -1) ? value.split(',') : [value];
        var regExp  = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;

        // First check to make sure we don't have too many emails
        if (emails.length > 5 ) return false;

        // Now check for valid emails
        for (var i = 0; i < emails.length; i++) {
            if (! regExp.test(emails[i])) {
                return false;
            }
        }

        return true;
        /* END SOLSPACE HACK */
        //var regExp  = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
        //return (regExp.test(emails[i]));
    };

    initialize();
  };


  /*  Modal Window
  ----------------------------------------------- */
  var AnimateOpacity = function(element, callback) {
    var animationTimer;
    var duration = 0.1;
    var intervalTime = 50;
    var numIntervals = duration * 1000 / intervalTime;

    if (animationTimer) clearInterval(animationTimer);
    animationTimer = setInterval(function() {
      var current = Number(element.style.opacity);
      var interval = (1 - current) / numIntervals;
      var opacity = current + interval;
      if ((1 - opacity) <= 0.05) {
        opacity = 1;
        if (animationTimer) clearInterval(animationTimer);
        if (callback) callback();
      }
      element.style.opacity = opacity;
    }, intervalTime);
  };
  var ModalWindow = function(element/* HTMLLinkElement */, config/* HashObject */) {
    if (!element) return;

    if (!config) config = {};

    var request/* XMLHttpRequest */;  // Ajax request object
    var outer/* HTMLElement */;       // Outermost element of the modal window (mask/shadow)
    var inner/* HTMLElement */;       // The modal window frame
    var width = config.width || 600;
    var height = config.height;
    var id;
    //var iframe;
    var body;
    var rendered = false;

    function initialize() {
      if (element.nodeName.toLowerCase() == "a") {
        if (element.href.indexOf("#") >= 0) {
          body = document.getElementById(element.href.split("#")[1]);
          if (body) body.parentNode.removeChild(body);
        }
        addListener(element, "click", onClick);
      } else {
        body = element;
        if (config.delaySeconds) {
          element.style.display = "none";
          setTimeout(function() {
            element.style.display = "block";
            onContentReady();
          }, config.delaySeconds * 1000);
        } else {
          onContentReady();
        }
      }
    }
    function onClick(e) {
      if (!rendered) {
        if (body) {
          onContentReady();
        } else {
          sendRequest();
        }
      } else {
        outer.style.display = "block";
        center();
      }
      preventDefault(e);
    };
    function sendRequest() {
      // IE7+, Firefox, Chrome, Opera, Safari
      if (window.XMLHttpRequest) {
        request = new XMLHttpRequest();
      // IE6, IE5
      } else {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      }
      request.onreadystatechange = onResponse;
      request.open("GET", element.href, true);
      request.send(null);
    };
    function onResponse() {
      if (request.readyState != 4) return;

      try {
        // If the request was successfull, open a modal window.
        if (request.status == 200) {
          onContentReady();
          return;
        }

      } catch(error) {
        if (window.console && window.console.log) window.console.log(error);
      }

      // If an error occurred while setting up the modal window, open a new window instead.
      window.open(element.href, "_blank");
      if (outer && outer.parentNode) outer.parentNode.removeChild(outer);
    };
    function onContentReady() {
        render();
        center();

        // Add validation to any forms in the modal window.
        var forms = outer.getElementsByTagName("form");
        for (var index = 0; index < forms.length; index++) {
          new ValidateForm(forms[index]);
        }

        // If there are any listeners in the queue, call them.
        if (typeof(ModalListeners) != "undefined") {
          for (var index = 0; index < ModalListeners.length; index++) {
            var listener = ModalListeners[index];
            if (listener[0] == id && typeof(listener[1]) == "function") {
              listener[1](outer);
            }
          }
        }

        return;
    }
    function render() {
      rendered = true;
      if (!body) body = getBody(request.responseText);

      // Create an outer element (for the page mask).
      outer = document.createElement("div");
      outer.id = "modal";
      document.body.appendChild(outer);

      // Create an innter element (to represent the modal window).
      inner = document.createElement("div");
      // If it's a string
      if (body.html) {
        inner.innerHTML = body.html;
        inner.id = body.id;
      // Else assume it's an element
      } else {
        inner.appendChild(body);
        body.style.display = "block";
      }
      id = body.id;
      inner.className = "inner";
      outer.appendChild(inner);

      var dimensions = getDimensions();
      outer.style.width = "100%";
      outer.style.height = dimensions.height + "px";

      if (config.transition) {
        outer.style.opacity = 0;
        inner.style.opacity = 0;
        new AnimateOpacity(outer, function() {
          new AnimateOpacity(inner);
        });
      }

      // Create a close button.
      var p = document.createElement("p");
      p.className = "close";
      inner.appendChild(p);
      var closeButton = document.createElement("a");
      closeButton.href = "#close";
      closeButton.appendChild(document.createTextNode("Close"));
      p.appendChild(closeButton);
      addListener(closeButton, "click", onCloseButtonClick);

      // Create a close link.
      if (!config.excludeCloseLink) {
        p = document.createElement("p");
        p.className = "close-last";
        inner.appendChild(p);
        var closeButton = document.createElement("a");
        closeButton.href = "#close";
        closeButton.appendChild(document.createTextNode("Close this window"));
        p.appendChild(closeButton);
        addListener(closeButton, "click", onCloseButtonClick);
      }

      var cancel = getOneElementByClassName(inner, "cancel");
      if (cancel) addListener(cancel, "click", onCloseButtonClick);

      try {
        var no = getOneElementByClassName(inner, "no");
        if (no) addListener(no.getElementsByTagName("a")[0], "click", onCloseButtonClick);
      } catch(e) {}

      // KLUDGE: Close the window if Foxycart buttons are clicked
      var foxyCartLinks = getAllElementsByClassName(inner, "foxycart_link");
      for (var index = 0; index < foxyCartLinks.length; index++) {
        if (String(foxyCartLinks[index].nodeName).toLowerCase() != "a") continue;
        (function() {
          var link = foxyCartLinks[index];
          addListener(link, "click", function() {
            setTimeout(function() { outer.style.display = "none"; }, 1);
          });
        })();
      }

      // Add placeholder text to fields, if needed.
      var fields = inner.getElementsByTagName("input");
      for (var index = 0; index < fields.length; index++) {
        var placeholderText = fields[index].getAttribute("placeholder");
        if (placeholderText != null && placeholderText != "") {
          addPlaceholderText(fields[index], placeholderText);
        }
      }

      if (config.easyEscape) {
        // If the user clicks on the mask, hide this window.
        addListener(outer, "click", function(e) {
          var target = e.target;
          if (!target && e.srcElement) target =  e.srcElement; // For IE
          if (target === outer) {
            hide();
          }
        });
      }

      // If the user presses "esc" hide this window.
      addListener(document, "keydown", function(e) {
          if (e && e.keyCode == 27) {
              hide();
          }
      });

      // Create an iframe, and position it underneath the window
      // (to work around an issue where Flash content sometimes appears above the window).
      /*
      iframe = document.createElement("iframe");
      inner.parentNode.insertBefore(iframe, inner);
      iframe.frameBorder = "0";
      iframe.src = "javascript:false";
      iframe.style.position = "absolute";
      iframe.style.borderWidth = "0";
      iframe.style.padding = "0";
      iframe.style.backgroundColor = "#fff";
      */
      //iframe.style.opacity = "0";

      // For IE
      //iframe.style.filter = "alpha(opacity=0)";
    };
    function center() {
      inner.style.width = width + "px";
      if (height) inner.style.height = height + "px";
      inner.style.overflow = "auto";

      var viewport = getViewportDimensions();
      var scroll = getScrollOffsets();
      var top = scroll.top + Math.floor((viewport.height - inner.offsetHeight) / 2);

      // Handle the case where the document is shorter than the modal window.
      if (inner.offsetHeight > viewport.height) top = scroll.top + 50;

      inner.style.marginTop = top + "px";

      /*
      iframe.style.left = inner.offsetLeft + "px";
      iframe.style.top = inner.offsetTop + "px";
      iframe.style.width = inner.offsetWidth + "px";
      iframe.style.height = inner.offsetHeight + "px";
      */
    };
    function getBody(responseText) {

      // Get the content of the body element.
      var regexp    = new RegExp("<body[^>]*>");
      var results   = responseText.match(regexp);
      var start     = results.index + results[0].length;
      var end       = responseText.indexOf("</body>");
      var html      = responseText.substring(start, end);

      // Get the id of the body element.
      regexp = new RegExp('id="([^"]*)"');
      results = results[0].match(regexp);
      var id = results[1];

      return {
        id: id,
        html: html
      };
    };
    function onCloseButtonClick(e) {
      if (e.preventDefault) e.preventDefault();
      e.returnValue = false; // For IE
      hide();
    };
    function hide() {
      outer.style.display = "none";
    };

    initialize();

    return {
      onClick: onClick
    };
  };


  /* =Audio Player
  ----------------------------------------------- */
  (function() {

    // Some browsers don't yet support <input type="range" />, so simulate one.
    var RangeField = function(element, min, max) {
      if (!element) return;

      var button;
      var background;
      var indicator;

      function initialize() {
        if (!min) min = 0;
        if (!max) max = 100;

        button = document.createElement("a");
        button.href = "#set-range";
        button.className = "range-field";
        background = document.createElement("span");
        background.className = "background";
        button.appendChild(background);
        indicator = document.createElement("span");
        indicator.className = "indicator";
        background.appendChild(indicator);
        element.parentNode.insertBefore(button, element);
        element.style.display = "none";

        setPercentage(getPercentage());

        addListener(button, "click", onClick);
      };

      function onClick(e) {
        preventDefault(e);
        var mouse = {
          x: e.clientX,
          y: e.clientY
        };
        var buttonCoordinates = getAbsolutePosition(button);
        var percentage = (mouse.x - buttonCoordinates.x) / button.offsetWidth;
        setPercentage(percentage);
        fireEvent(element, "change");
      };

      function setPercentage(percentage) {
        indicator.style.width = String(Math.round(percentage * 100)) + "%";
        element.value = Math.floor((max - min) * percentage);
      }

      function getPercentage() {
        var range = Math.floor(max - min);
        if (range <= 0) return 0;
        return Number(element.value) / range;
      }

      function update() {
        indicator.style.width = String(getPercentage() * 100) + "%";
      }

      initialize();

      return {
        update: update,
        setMaximum: function(maximum) {
          max = maximum;
        }
      };
    };

    // Provide an interface for an <audio /> element.
    // If the browser doesn't support it, create a Flash movie to simlulate it.
    var AudioElement = function(element, simulate, callbackName) {
      if (simulate == undefined) simulate = false;

      var audiofile;
      var div;
      var swf;
      var autoplay = false;

      function setswf() {
        swf = div.getElementsByTagName("object")[0];
      }

      function initialize() {
        if (simulate) {
          var html = element.innerHTML;
          if (html == "") html = element.parentNode.innerHTML;  // IE8 doesn't return anything for element.innerHTML, strangely.
          var regex = new RegExp("src=\"(.+\.mp3)\"");
          var results = regex.exec(html);
          var base = (results[1].indexOf("http") >= 0) ? "" : window.location.protocol + "//" + window.location.host;
          audiofile = base + results[1];
          autoplay = (html.indexOf("autoplay=") >= 0);
          /*
          var sources = element.getElementsByTagName("source");
          console.log("sources: " + sources.length);
          for (var index = 0; index < sources.length; index++) {
            console.log("sources[index]: " + sources[index]);
            if (sources[index].getAttribute("src").indexOf("mp3") >= 0) {
              audiofile = sources[index].getAttribute("src");
              console.log("audiofile: " + audiofile);
            }
          }
          */
          // Create an invisible swf object.
          var swfpath = "/swf/audio.swf?callback=" + callbackName + "&audiofile=" + encodeURIComponent(audiofile);
          div = document.createElement("div");
          div.style.position = "absolute";
          div.style.bottom = "0";
          div.style.left = "0";
          document.body.appendChild(div);

          // TRICKY: The Flash element must have an "id" and a "type" for the external interface to work in Internet Explorer.
          // http://mihai.bazon.net/blog/flash-s-externalinterface-and-ie
          // http://synja.com/?p=35
          var now = new Date();
          var random = Math.floor(Math.random() * 1000001);
          var id = "flashelement" + now.getTime() + random;
          div.innerHTML =
          '<object id="' + id + '" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" type="application/x-shockwave-flash" width="1" height="1" data="' + swfpath + '">' +
          '<param name="movie" value="' + swfpath + '" />' +
          '<param name="allowscriptaccess" value="always" />' +
          '</object>';
        }
      };
      initialize();

      if (simulate) {
        return {
          setswf: setswf,
          play            : function()      { swf._audioelement_play(); },
          pause           : function()      { swf._audioelement_pause(); },
          mute            : function()      { swf._audioelement_mute(); },
          unMute          : function()      { swf._audioelement_unMute(); },
          setCurrentTime  : function(time)  { swf._audioelement_setCurrentTime(time); },
          setVolume       : function(level) { swf._audioelement_setVolume(level); },
          getDuration     : function()      { return swf._audioelement_getDuration(); },
          getCurrentTime  : function()      { return swf._audioelement_getCurrentTime(); },
          isAutoPlay      : function()      { return autoplay; },
          isPaused        : function()      { return swf._audioelement_isPaused(); },
          isMuted         : function()      { return swf._audioelement_isMuted(); }
        };
      } else {
        return {
          setswf: setswf,
          play: function() {
            element.play();
          },
          pause: function() {
            element.pause();
          },
          mute: function() {
            element.muted = true;
          },
          unMute: function() {
            element.muted = false;
          },
          setCurrentTime: function(time) {
            element.currentTime = time;
          },
          setVolume: function(level) {
            element.volume = level;
          },
          getDuration: function() {
            return element.duration;
          },
          getCurrentTime: function() {
            return element.currentTime;
          },
          isAutoPlay: function() {
            return element.autoplay;
          },
          isPaused: function() {
            return element.paused;
          },
          isMuted: function() {
            return element.muted;
          }
        };
      }
    };

    // The built-in browser controls for <audio /> aren't styleable,
    // so replace them with HTML elements that can be styled.
    var AudioPlayer = function(element) {
      if (!element) return;

      var dispatcher      = new EventDispatcher();
      var dispatcherAddListener     = dispatcher.addListener;
      var dispatchEvent   = dispatcher.dispatchEvent;
      var audio;
      var form;
      var playButton;
      var muteButton;
      var duration;
      var elapsed;
      var seconds;
      var secondsRangeField;
      var volume;
      var timer;
      var lastDuration;
      var showDurationTimeout;
      var durationSet = false;
      var loadedAnnounced = false;
      var simulate;

      function initialize() {
        simulate = !supportsAudio();

        // Create a unique callback function.
        var now = new Date();
        var random = Math.floor(Math.random() * 1000001);
        var callbackName = "flashaudio" + now.getTime() + random;
        var callbackCalled = false;

        if (simulate) {
          window[callbackName] = function() {
            if (callbackCalled) {
              updateDuration();
              return;
            }
            callbackCalled = true;
            audio.setswf();
            _initialize();
          };
          audio = new AudioElement(element, simulate, callbackName);
        } else {
          audio = new AudioElement(element);
          _initialize();
        }
      };
      function _initialize() {
        render();

        // Start the volume level at 50%.
        volume.value = 50;

        // Sync the volume with the control.
        updateVolume();

        // Show the duration, as soon as it becomes available.
        var tries = 0;
        timer = setInterval(function() {
          tries++;
          if (tries > 2000) {  // Try for twenty seconds.
            if (timer) clearInterval(timer);
          }
          if (audio.getDuration() != null && !isNaN(audio.getDuration()) && !audio.getDuration() == 0) {
            if (timer) clearInterval(timer);
            onDurationReady();
          }
        }, 100);

        addListener(playButton, "click", onPlayButtonClick);
        addListener(muteButton, "click", onMuteButtonClick);
        addListener(volume, "change", updateVolume);
        addListener(seconds, "change", updateTime);
        addListener(form, "submit", function(e) {
          preventDefault(e);
        });

        new RangeField(volume);
        secondsRangeField = new RangeField(seconds);

        element.style.display = "none";

        /* For Mobile Safari (display:none does not appear to be supported on <audio /> elements) */
        element.style.position = "absolute";
        element.style.left = "-9999px";
      };

      function supportsAudio() {
        return !!document.createElement("audio").canPlayType;
      };

      function onDurationReady() {
        if (durationSet) return;
        durationSet = true;
        updateDuration();

        if (audio.isAutoPlay()) {
          if (simulate) {
            play();
          } else {
            if (timer) clearInterval(timer);
            timer = setInterval(function() {
              // If the audio has started playing...
              if (!audio.isPaused()) {
                if (timer) clearInterval(timer);
                // Behave as though the play button was clicked.
                play();
              }
            }, 100);
          }
        }
      }

      function updateDuration() {
        var max = Math.floor(audio.getDuration());
        if (isNaN(audio.getDuration())) max = 100;
        seconds.max = max;
        secondsRangeField.setMaximum(max);
        showTime();
      };

      function onPlayButtonClick(e) {
        preventDefault(e);
        toggle();
      };

      function onMuteButtonClick(e) {
        preventDefault(e);
        if (audio.isMuted()) {
          audio.unMute();
          muteButton.innerHTML = "Mute";
          muteButton.parentNode.className = "mute";
        } else {
          audio.mute();
          muteButton.innerHTML = "Unmute";
          muteButton.parentNode.className = "unmute";
        }
      };

      function toggle() {
        if (audio.isPaused()) {
          play();
        } else {
          pause();
        }
      };

      function play() {
        if (audio.getCurrentTime() >= audio.getDuration()) {
          rewind();
          setTimeout(play, 1);
          return;
        }
        if (audio.isPaused()) audio.play();
        onDurationReady();
        if (timer) clearInterval(timer);
        checkTime();
        // Every quarter-second, update the time displayed to the user.
        timer = setInterval(checkTime, 250);
        playButton.innerHTML = "Pause";
        playButton.parentNode.className = "pause";
        dispatchEvent("play");
      };

      function pause() {
        audio.pause();
        if (timer) clearInterval(timer);
        timer = null;
        playButton.innerHTML = "Play";
        playButton.parentNode.className = "play";
      };

      function updateTime() {
        audio.setCurrentTime(Number(seconds.value));
        showTime();
      };

      function updateVolume() {
        audio.setVolume(Number(volume.value) / 100);
      };

      function showTime() {

        var newDuration = audio.getDuration();
        if (!lastDuration || (newDuration > lastDuration)) {
          // Update the duration after a short delay (effectively waiting until the value stabilizes).
          //if (showDurationTimeout) clearTimeout(showDurationTimeout);
          //showDurationTimeout = setTimeout(function() {
            duration.innerHTML  = getFormattedTime(newDuration);
            lastDuration = newDuration;
          //}, 100);
        }

        elapsed.innerHTML   = getFormattedTime(audio.getCurrentTime());
      };

      function checkTime() {
        if (audio.getCurrentTime() > 0 && !loadedAnnounced) {
          dispatchEvent("loaded");
          loadedAnnounced = true;
        }
        showTime();
        if (!seconds.hasFocus) {
          seconds.value = audio.getCurrentTime();
          secondsRangeField.update();
        }
        if (audio.getCurrentTime() >= audio.getDuration()) {
          pause();
        }
      };

      function getFormattedTime(seconds) {
        if (seconds == null || isNaN(seconds)) {
          seconds = 0;
        }
        seconds = Math.floor(seconds);
        var minutes = Math.floor(seconds / 60);
        seconds = seconds % 60;
        if (minutes < 10) minutes = "0" + minutes;
        if (seconds < 10) seconds = "0" + seconds;
        return minutes + ":" + seconds;
      };

      function rewind() {
        seconds.value = 0;
        updateTime();
        secondsRangeField.update();
      };

      function render() {
        form = document.createElement("form");
        form.onsubmit = function(e) {
          preventDefault(e);
          return false;
        };
        form.className = "audio-player";
        element.parentNode.insertBefore(form, element);
        form.innerHTML =
        '<p class="play"><button>Play</button></p>' +
        '<p class="seconds">' +
        '  <label>' +
        '    <span>Play Head</span>' +
        '    <input name="seconds" type="range" min="0" max="100" value="0" />' +
        '  </label>' +
        '</p>' +
        '<p class="time"><span class="elapsed">00:00</span> / <span class="duration">00:00</span></p>' +
        '<p class="mute"><button>Mute</button></p>' +
        '<p class="volume">' +
        '  <label>' +
        '    <span>Volume</span>' +
        '    <input name="volume" type="range" min="0" max="100" value="0" />' +
        '  </label>' +
        '</p>';

        playButton = getOneElementByClassName(form, "play").getElementsByTagName("button")[0];
        muteButton = getOneElementByClassName(form, "mute").getElementsByTagName("button")[0];
        duration = getOneElementByClassName(form, "duration");
        elapsed = getOneElementByClassName(form, "elapsed");
        seconds = form.seconds;
        volume = form.volume;
      };

      initialize();

      return {
        play: play,
        pause: pause,
        isAutoPlay: function() {
          if (audio) return audio.isAutoPlay();
        },
        addListener: dispatcherAddListener
      };

    };


    (function() {
      var tries = 0;

      function initialize() {
        waitForBody();
      };

      function waitForBody() {
        var body = document.getElementsByTagName("body");
        if (body == null || body.length <= 0) {
          // If we haven't been trying for more than 5 seconds
          if (tries++ < 50) {
            // Wait 1/10 second, and try again
            setTimeout(waitForBody, 100);
          }
        } else {
          tries = 0;
          // Only look for audio players on the home and conversations pages
          // (at the time of this writing, those are the only documents in which
          // audio players appear).
          if (body[0].id == "podcasts-page" ||
                     body[0].id == "conversations-page" ||
                     body[0].id == "conversations-library-page" ||
                     body[0].id == "conversations-promo-page") {
            waitForOneAudio();
          }
        }
      };

      function waitForOneAudio() {
        var audio = document.getElementsByTagName("audio");
        if (audio && audio.length > 0) {
          for (var index = 0; index < audio.length; index++) {
            var player = new AudioPlayer(audio[index]);
            if (document.body.id == "podcasts-page" ||
            	document.body.id == "conversations-library-page" ||
                document.body.id == "conversations-promo-page") {
              if (player.isAutoPlay()) {
                var spinnerContainer = document.createElement("div");
                var size = 60;
                spinnerContainer.style.width = size + "px";
                spinnerContainer.style.height = size + "px";
                spinnerContainer.style.position = "absolute";
                spinnerContainer.style.top = String(((267 - size) / 2) + 15) + "px";
                spinnerContainer.style.left = String(((480 - size) / 2) + 15) + "px";
                var library = getOneElementByClassName(document.getElementById("content-main"), "conversations-library");
                library.appendChild(spinnerContainer);
                spinner = new Spinner(spinnerContainer, {size: size});
                player.addListener("loaded", function(e) {
                  if (spinner && spinner.stop) spinner.stop();
                  if (spinnerContainer) spinnerContainer.parentNode.removeChild(spinnerContainer);
                });
              } else {
                // Create a play button
                var playButton = document.createElement("a");
                playButton.href = "#play";
                playButton.innerHTML = "Play";
                playButton.className = "play-button";
                var size = 64;
                playButton.style.width = size + "px";
                playButton.style.height = size + "px";
                playButton.style.position = "absolute";
                playButton.style.top = String(((267 - size) / 2) + 15) + "px";
                playButton.style.left = String(((480 - size) / 2) + 15) + "px";
                var library = getOneElementByClassName(document.getElementById("content-main"), "conversations-library");
                library.appendChild(playButton);
                addListener(playButton, "click", function(e) {
                  preventDefault(e);
                  playButton.parentNode.removeChild(playButton);
                  var spinnerContainer = document.createElement("div");
                  var size = 60;
                  spinnerContainer.style.width = size + "px";
                  spinnerContainer.style.height = size + "px";
                  spinnerContainer.style.position = "absolute";
                  spinnerContainer.style.top = String(((267 - size) / 2) + 15) + "px";
                  spinnerContainer.style.left = String(((480 - size) / 2) + 15) + "px";
                  var library = getOneElementByClassName(document.getElementById("content-main"), "conversations-library");
                  library.appendChild(spinnerContainer);
                  spinner = new Spinner(spinnerContainer, {size: size});
                  player.addListener("loaded", function(e) {
                    if (spinner && spinner.stop) spinner.stop();
                    if (spinnerContainer) spinnerContainer.parentNode.removeChild(spinnerContainer);
                  });
                  player.play();
                });
                player.addListener("play", function(e) {
                  if (playButton && playButton.parentNode) playButton.parentNode.removeChild(playButton);
                });
              }
            }
          }
        } else {
          // If we haven't been trying for more than 5 seconds
          if (tries++ < 50) {
            // Wait 1/10 second, and try again
            setTimeout(waitForOneAudio, 100);
          }
        }
      };

      function waitForTwoAudio() {
        var audio = document.getElementsByTagName("audio");
        // KLUDGE: Wait for at least two audio players.
        // (It would be better if it worked regardless of how many there are.)
        if (audio && audio.length > 1) {
          for (var index = 0; index < audio.length; index++) {
            for (var index = 0; index < audio.length; index++) {
              var player = new AudioPlayer(audio[index]);
            }
          }
        } else {
          // If we haven't been trying for more than 5 seconds
          if (tries++ < 50) {
            // Wait 1/10 second, and try again
            setTimeout(waitForTwoAudio, 100);
          }
        }
      };

      initialize();
    })();
  })();


  /*  =SummaryText
  ----------------------------------------------- */
  var SummaryText = function(element) {
    if (!element) return;

    var tries = 0;

    function initialize() {
      if (!moreThanFourLines()) return;

      // 1) Check the height of the text to see if it's more than four lines.
      // 2) If it is, remove the last word (ignoring the ellipses and link)
      // and repeat step 1. Otherwise quit.
      var words = document.createElement("span");
      words.innerHTML = element.innerHTML;
      element.innerHTML = "";
      element.appendChild(words);

      while (moreThanFourLines() && tries < 100) {
        removeWord(words);
        tries++;
      }
    };

    function moreThanFourLines() {

      // Temporarily remove the minimum height,
      // so it won't interere with the calculations.
      var minHeight = element.style.minHeight;
      element.style.minHeight = "0";

      var height = element.offsetHeight;
      element.style.lineHeight = "1.55";
      element.style.height = "6.2em";
      element.style.overflow = "hidden";
      var newHeight = element.offsetHeight;
      element.style.height = "auto";
      element.style.overflow = "visible";

      // Restore the minimum height.
      element.style.minHeight = minHeight;
      return (newHeight < height);
    };

    function isAlphaNumeric(character) {
      var target = String(character).toLowerCase();
      var alpha = "abcdefghijklmnopqrstuvwxyz0123456789";
      return (alpha.indexOf(target) >= 0);
    };

    function removeWord(element) {

      // Temporarily remove the link.
      var link = element.getElementsByTagName("a")[0];
      link.parentNode.removeChild(link);

      // Remove any whitespace at the end of the element.
      // KUDOS: http://www.somacon.com/p355.php
      var text = element.innerHTML;
      text = text.replace(/\s+$/,"");

      // Remove the last word.
      var words = text.split(" ");
      words.pop();
      while(words[words.length - 1].length <= 1) {
        // If the last word is a single character, remove it too.
        words.pop();
      }
      text = words.join(" ");

      // Remove the last character, if it's not a letter or number.
      if (!isAlphaNumeric(text.charAt(text.length - 1))) {
        text = text.substring(0, text.length - 1);
      }
      element.innerHTML = text;

      // Add an ellipses (it was likely removed, along with the last word).
      var ellipses = document.createTextNode("… ");
      element.appendChild(ellipses);

      // Add the link back.
      element.appendChild(link);
    };

    initialize();
  };


  /* =Featured Slide Show
  ----------------------------------------------- */
  var SlideShow = function(element, config) {
    if (!element) return;
    if (!config) config = {};

    var horizontal = false;
    var outer;
    var inner;
    var nextButton;
    var previousButton;
    var position = 0;
    var viewportSize;     // The size of the viewport (i.e. the height or width of the first set of items).
    var itemSize;         // The size of a typical item (i.e. any item except the first one).
    var setSize;          // The size of the number of items that can fit within the viewport
                          // (this is actually slightly larger than the viewport, to account for
                          // the margin after the last item in the set).
    var itemsPerSet = 3;
    var itemsPerRow;
    var rowsPerSet = 1;
    var animationTimer;
    var autoTimer;
    var duration = 0.15;
    var intervalTime = 50;
    var numIntervals = duration * 1000 / intervalTime;
    var loadCursor = 0;
    var items;

    var loop = true;
    var autorun = true;
    var autoDelay = 10;    // Seconds

    function initialize() {
      if (config.itemsPerSet) itemsPerSet = config.itemsPerSet;
      if (config.rowsPerSet) rowsPerSet = config.rowsPerSet;
      if (config.calculateSizes) calculateSizes = config.calculateSizes;
      if (config.horizontal != null) horizontal = config.horizontal;
      if (config.autorun != null) autorun = config.autorun;
      if (config.loop != null) loop = config.loop;

      items = getAllElementsByClassName(element, "item");
      inner = getOneElementByClassName(element, "inner");
      outer = getOneElementByClassName(element, "outer");

      itemsPerRow = itemsPerSet / rowsPerSet;
      var sizes = calculateSizes(items, itemsPerRow);
      itemSize = sizes.itemSize;
      setSize = sizes.setSize;
      viewportSize = sizes.viewportSize;

      if (rowsPerSet > 1) {

        // Position the elements in a grid
        (function() {
          var top = 0;
          var left = 0;
          var rowNumber = 1;
          var maxRows = 0;
          var numSets = 0;
          var subGridLeft = 0;
          var itemHeight = items[0].offsetHeight + 2;
          for (var index = 0; index < items.length; index++) {

            if (index != 0) {
              // If we've reached the end of a row, start a new one.
              if (index % itemsPerRow == 0) {
                rowNumber++;

                // If we've reached the last available row, start a new set.
                if (rowNumber > rowsPerSet) {
                  numSets++;
                  rowNumber = 1;
                  top = 0;
                  subGridLeft = itemSize * itemsPerRow * numSets;
                } else {
                  top += itemHeight;
                }

                if (rowNumber > maxRows) maxRows = rowNumber;
                left = subGridLeft;

              // Otherwise, continue on to the next position in the row.
              } else {
                left += itemSize;
              }
            }

            items[index].style.position = "absolute";
            items[index].style.top = top + "px";
            items[index].style.left = left + "px";
          }
          items[0].parentNode.style.height = itemHeight * maxRows + "px";
        })();

      }

      if (horizontal) {
        var numSets = items.length / itemsPerSet;
        inner.style.width = (itemSize * itemsPerRow * numSets) + "px";
      }

      addClassName(element, "loaded");
      if (horizontal) {
        outer.style.width = viewportSize + "px";
      } else {
        outer.style.height = viewportSize + "px";
      }
      outer.style.overflow = "hidden";
      outer.style.position = "relative";
      inner.style.position = "relative";

      // If all of the items are visible, stop here.
      if (atStart() && atEnd()) {
        return;
      }

      render();
      updateButtonState();

      addListener(nextButton, "click", onNextClick);
      addListener(previousButton, "click", onPreviousClick);

      if (autorun) {
        delayAutoNext();
        addListener(element, "mousemove", onUserAction);
      }
    };

    function onUserAction(e) {
      delayAutoNext();
    };

    function delayAutoNext(seconds) {
      if (seconds == null) seconds = autoDelay;
      if (autoTimer) clearTimeout(autoTimer);
      autoTimer = setTimeout(autoNext, seconds * 1000);
    };

    function autoNext() {
      next();
      delayAutoNext();
    };

    function calculateSizes() {}

    function atStart() {
      return position >= 0;
    };

    function atEnd() {
      var movableDistance = horizontal ? inner.offsetWidth - setSize : inner.offsetHeight - setSize;
      if (movableDistance < 0) movableDistance = 0;
      return (position) <= (0 - movableDistance);
    };

    function loadMore() {
      for (var count = 1; count <= itemsPerRow; count++) {

        /*
        TODO: Move the items instead of cloning them, if there are enough.

        var item = items[loadCursor];
        if (items.length >= 9) item.parentNode.removeChild(item);
        else item = item.cloneNode(true);
        */

        var item = items[loadCursor].cloneNode(true);
        inner.appendChild(item);
        items.push(item);
        loadCursor++;
      }
    };

    function render() {
      var list = document.createElement("ul");
      element.appendChild(list);

      var item;

      item = document.createElement("li");
      item.className = "next";
      list.appendChild(item);

      nextButton = document.createElement("a");
      nextButton.href = "#next";
      nextButton.appendChild(document.createTextNode("Next"));
      item.appendChild(nextButton);

      item = document.createElement("li");
      item.className = "previous";
      list.appendChild(item);

      previousButton = document.createElement("a");
      previousButton.href = "#next";
      previousButton.appendChild(document.createTextNode("Previous"));
      item.appendChild(previousButton);
    };

    function onNextClick(e) {
      preventDefault(e);
      next();
    };

    function onPreviousClick(e) {
      preventDefault(e);
      previous();
    };

    function next() {
      if (atEnd()) return;
      position -= setSize;
      if (atEnd() && loop) loadMore();
      moveInner(position);
      updateButtonState();
    };

    function previous() {
      if (atStart()) return;
      position += setSize;
      moveInner(position);
      updateButtonState();
    };

    function moveInner(target) {
      var prop = horizontal ? "left" : "top";
      if (animationTimer) clearInterval(animationTimer);
      animationTimer = setInterval(function() {
        var current = Number(inner.style[prop].replace("px", ""));
        if (target < current) {
          var interval = (current - target) / numIntervals;
          var next = Math.floor(current - interval);
        } else {
          var interval = (target - current) / numIntervals;
          var next = Math.ceil(current + interval);
        }
        if (Math.abs(target - next) <= 1) {
          next = target;
          if (animationTimer) clearInterval(animationTimer);
        }
        inner.style[prop] = next + "px";
      }, intervalTime);
    };

    function updateButtonState() {
      if (!nextButton || !previousButton) return;
      if (atEnd()) {
        addClassName(nextButton, "disabled");
      } else {
        removeClassName(nextButton, "disabled");
      }
      if (atStart()) {
        addClassName(previousButton, "disabled");
      } else {
        removeClassName(previousButton, "disabled");
      }
    };

    initialize();

    return {
      scrollTo: function(targetIndex) {

        // Find the set number where the target is located.
        var setNumber = Math.floor(targetIndex / itemsPerSet);

        // Scroll to that set.
        position = 0 - (setNumber * setSize);
        if (atEnd() && loop) loadMore();
        moveInner(position);
        updateButtonState();
      }
    };
  };

  var AdRotator = function(element) {
    if (!element) return;

    var animationTimer;
    var autoTimer;
    var duration = 0.15;
    var intervalTime = 50;
    var numIntervals = duration * 1000 / intervalTime;
    var items;
    var cursor = 0;
    var depth = 0;

    var autoDelay = 4;    // Seconds

    function initialize() {
      items = getAllElementsByClassName(element, "ad");
      if (items.length <= 1) return;  // If there's only one ad, do nothing.

      element.style.height = items[0].offsetHeight + "px";
      element.style.position = "relative";
      element.style.overflow = "hidden";

      for (var index = 0; index < items.length; index++) {
        items[index].style.position = "absolute";
        items[index].style.width = "100%";
      }

      cursor = Math.floor((Math.random() * 3)); // Random number from 0-3
      items[cursor].style.zIndex = ++depth;

      delayAutoNext();
      addListener(element, "mousemove", onUserAction);
    };

    function onUserAction(e) {
      delayAutoNext();
    };

    function delayAutoNext(seconds) {
      if (seconds == null) seconds = autoDelay;
      if (autoTimer) clearTimeout(autoTimer);
      autoTimer = setTimeout(autoNext, seconds * 1000);
    };

    function autoNext() {
      next();
      delayAutoNext();
    };

    function next() {
      cursor++;
      if (cursor >= items.length) cursor = 0;
      var item = items[cursor];
      item.style.opacity = 0;
      item.style.zIndex = ++depth;

      if (animationTimer) clearInterval(animationTimer);
      animationTimer = setInterval(function() {
        var current = Number(item.style.opacity);
        var interval = (1 - current) / numIntervals;
        var opacity = current + interval;
        if ((1 - opacity) <= 0.05) {
          opacity = 1;
          if (animationTimer) clearInterval(animationTimer);
        }
        item.style.opacity = opacity;
      }, intervalTime);
    };

    initialize();
  };

  (function() {

    var tries = 0;

    function initialize() {
      waitForBody();
    };

    function waitForBody() {
      var body = document.getElementsByTagName("body");
      if (body == null || body.length <= 0) {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForBody, 100);
        }
      } else {
        tries = 0;
        if (body[0].id == "home-page") {
          //waitForFeatured();
          waitForMainContent();
          
          var cookie = Cookie.get("insightstoutshown");
          if (!cookie) {
            waitForInsightsTout();
          }
        }
        if (body[0].id == "home-page" ||
            body[0].id == "tag-page" ||
            body[0].id == "newsletter-page" ||
            body[0].id == "archive-page") {
          waitForAds();
        }
      }
    };

    function waitForMainContent() {
      var target = document.getElementById("content-main");
      if (target) {
        tries = 0;
        waitForConversations(target);
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForMainContent, 100);
        }
      }
    };

    function waitForConversations(mainContent) {

      // Once the converstations are available in the DOM, we know that the publications are ready.
      var target = getOneElementByClassName(mainContent, "product-conversations");
      if (target) {
        tries = 0;
        new SummaryText(getOneElementByClassName(mainContent, "frontline").getElementsByTagName("p")[0]);
        new SummaryText(getOneElementByClassName(mainContent, "mauldin-circle").getElementsByTagName("p")[0]);
        new SummaryText(getOneElementByClassName(mainContent, "outside-the-box").getElementsByTagName("p")[0]);
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForConversations, 100);
        }
      }
    };

    function waitForFeatured() {
      var target = document.getElementById("featured");
      if (target) {
        tries = 0;
        new SlideShow(target, {
          calculateSizes: function(items, itemsPerSet) {
            try {
              //var firstItemHeight = items[0].offsetHeight + items[0].offsetTop;
              //var itemTopMargin = items[1].offsetTop - firstItemHeight;
              var itemHeight = 61;
              var setHeight = itemHeight * itemsPerSet;
              var viewportHeight = 170;
            } catch(e) {
              console.error(e);
              return;
            }
            return {
              itemSize: itemHeight,
              setSize: setHeight,
              viewportSize: viewportHeight
            };
          }
        });
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForFeatured, 100);
        }
      }
    };

    function waitForAds() {
      var target = document.getElementById("aside-section-ads");
      if (target) {
        tries = 0;
        // Wait a brief moment, so the items within the featured element have time to load.
        //setTimeout(function() {
          new AdRotator(target);
        //}, 100);
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForAds, 100);
        }
      }
    };

    function waitForInsightsTout() {
      var target = document.getElementById("insights-tout");
      if (target) {
        tries = 0;

        new ModalWindow(target, {
          width: 700,
          excludeCloseLink: true,
          transition: true,
          delaySeconds: 3,
          easyEscape: true
        });  

        Cookie.set("insightstoutshown", 7);

      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForAds, 100);
        }
      }
    }

    initialize();

  })();


  /* =Tabs (Home Page)
  ----------------------------------------------- */
  (function() {

    var Tabs = function(element) {
      if (!element) return;
      var nav = getOneElementByClassName(element, "nav");
      var buttons = nav.getElementsByTagName("li");
      var tabs = [];
      for (var index = 0; index < buttons.length; index++) {
        (function() {
          var button = buttons[index];
          var link = button.getElementsByTagName("a")[0];
          var id = link.href.split("#")[1];
          if (!id) return;
          var tab = document.getElementById(id);
          if (!tab) return;
          tabs.push(tab);
          addListener(link, "click", function(e) {
            preventDefault(e);
            currentTab.style.display = "none";
            currentTab = tab;
            currentTab.style.display = "block";
            removeClassName(currentButton, "active");
            currentButton = button;
            addClassName(currentButton, "active");
          });
        })();
      }
      for (var index = 1; index < tabs.length; index++) {
        tabs[index].style.display = "none";
      }
      var currentTab = tabs[0];
      var currentButton = buttons[0];
      addClassName(currentButton, "active");
    };

    var tries = 0;

    function initialize() {
      waitForBody();
    };

    function waitForBody() {
      var body = document.getElementsByTagName("body");
      if (body == null || body.length <= 0) {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForBody, 100);
        }
      } else {
        tries = 0;
        // Only look for a featured component on the home page (at the time of this
        // writing, that's the only document in which it appears).
        if (body[0].id == "home-page") {
          waitForArticles();
        }
      }
    };

    function waitForArticles(mainContent) {
      var target = document.getElementById("aside-section-articles");
      if (target) {
        tries = 0;

        // Wait a brief moment, so the items within the articles element have time to load.
        setTimeout(function() {
          new Tabs(target);
        }, 100);

      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForArticles, 100);
        }
      }
    };

    initialize();

  })();


  /* =Tabs (Conversations Library)
  ----------------------------------------------- */
  (function() {

    var Tabs = function(element) {
      if (!element) return;
      var nav = getOneElementByClassName(element, "categories");
      var buttons = nav.getElementsByTagName("li");
      var tabs = [];
      var defaultIndex = 0;
      for (var index = 0; index < buttons.length; index++) {
        (function() {
          var button = buttons[index];
          var link = button.getElementsByTagName("a")[0];
          var id = link.href.split("#")[1];
          if (!id) return;
          var tab = document.getElementById(id);
          if (!tab) return;
          tabs.push(tab);
          addListener(link, "click", function(e) {
            preventDefault(e);
            currentTab.style.display = "none";
            currentTab = tab;
            currentTab.style.display = "block";
            removeClassName(currentButton, "active");
            currentButton = button;
            addClassName(currentButton, "active");
          });

          // By default, show the tab that contains the active item.
          if (tab.innerHTML.indexOf("active-conversations-library-item") >= 0) {
            defaultIndex = index;
          }
        })();
      }
      for (var index = 0; index < tabs.length; index++) {
        if (index != defaultIndex) tabs[index].style.display = "none";
      }
      var currentTab = tabs[defaultIndex];
      var currentButton = buttons[defaultIndex];
      addClassName(currentButton, "active");
    };

    var tries = 0;

    function initialize() {
      waitForBody();
    };

    function waitForBody() {
      var body = document.getElementsByTagName("body");
      if (body == null || body.length <= 0) {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForBody, 100);
        }
      } else {
        tries = 0;
        // Only look for a featured component on the home page (at the time of this
        // writing, that's the only document in which it appears).
        if (body[0].id == "conversations-library-page") {
          waitForFooter();
        }
      }
    };

    function waitForFooter(mainContent) {
      var target = document.getElementById("footer");
      if (target) {
        tries = 0;
        var library = getOneElementByClassName(document.getElementById("content-main"), "conversations-library");
        new Tabs(library);
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForFooter, 100);
        }
      }
    };

    initialize();

  })();


  /* =Placeholder Text
  -----------------------------------------------
  Simulate placeholder text, in browsers that don't support it yet.
  ----------------------------------------------- */
  (function() {

    var tries = 0;

    function initialize() {
      if (!supportsInputPlaceholder() || !supportsTextAreaPlaceholder()) {
        waitForBody();
      }
    };

    function waitForBody() {
      var body = document.getElementsByTagName("body");
      if (body == null || body.length <= 0) {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForBody, 100);
        }
      } else {
        tries = 0;

        if (!supportsInputPlaceholder()) {
          waitForSearchField();
          waitForAnotherSubscribeField();
          waitForSubscribeField();

          // Only search for member forms on the "edit profile" page.
          if (document.body.id == "profile-edit-page") {
            waitForMemberForm();
          }

          if (document.body.id == "conversations-page") {
            waitForSubscribeForm();
          }

          if (document.body.id == "over-shoulder-library-page") {
            waitForAsideSearchForm();
          }
        }

        if (!supportsTextAreaPlaceholder()) {
          // Only look for discussion fields on the newsletter and conversations pages (at the time of this
          // writing, those are the only documents in which they appear).
          if (body[0].id == "newsletter-page" || body[0].id == "conversations-library-page") {
            waitForFooter();
          }
        }
      }
    };

    function waitForFooter() {
      var target = document.getElementById("footer");
      if (target) {
        tries = 0;

        waitForDiscussField();
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForFooter, 100);
        }
      }
    };

    function waitForAsideSearchForm() {
      var aside = getOneElementByClassName(document.body, "aside");
      var target;
      if (aside) {
        target = getOneElementByClassName(aside, "search");
      }
      if (target) {
        tries = 0;
        var fields = target.getElementsByTagName("input");
        for (var index = 0; index < fields.length; index++) {
          var placeholderText = fields[index].getAttribute("placeholder");
          if (placeholderText != null && placeholderText != "") {
            addPlaceholderText(fields[index], placeholderText);
          }
        }
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForSearchField, 100);
        }
      }
    };

    function waitForSearchField() {
      var target = document.getElementById("search");
      if (target) {
        tries = 0;
        var fields = target.getElementsByTagName("input");
        for (var index = 0; index < fields.length; index++) {
          var placeholderText = fields[index].getAttribute("placeholder");
          if (placeholderText != null && placeholderText != "") {
            addPlaceholderText(fields[index], placeholderText);
          }
        }
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForSearchField, 100);
        }
      }
    };

    function waitForSubscribeField() {
      var target = document.getElementById("subscribe");
      if (target) {
        tries = 0;
        var fields = target.getElementsByTagName("input");
        for (var index = 0; index < fields.length; index++) {
          var placeholderText = fields[index].getAttribute("placeholder");
          if (placeholderText != null && placeholderText != "") {
            addPlaceholderText(fields[index], placeholderText);
            tries = 0;
          }
        }
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForSubscribeField, 100);
        }
      }
    };

    function waitForAnotherSubscribeField() {
      var target = document.getElementById("another-subscribe");
      if (target) {
        tries = 0;
        var fields = target.getElementsByTagName("input");
        for (var index = 0; index < fields.length; index++) {
          var placeholderText = fields[index].getAttribute("placeholder");
          if (placeholderText != null && placeholderText != "") {
            addPlaceholderText(fields[index], placeholderText);
            tries = 0;
          }
        }
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForAnotherSubscribeField, 100);
        }
      }
    };

    function waitForDiscussField() {
      var target;
      var mainContent = document.getElementById("content-main");
      if (mainContent) {
        target = getOneElementByClassName(mainContent, "discussion");
      }
      if (target) {
        tries = 0;
        var fields = target.getElementsByTagName("textarea");
        for (var index = 0; index < fields.length; index++) {
          var placeholderText = fields[index].getAttribute("placeholder");
          if (placeholderText != null && placeholderText != "") {
            addPlaceholderText(fields[index], placeholderText);
            tries = 0;
          }
        }
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForDiscussField, 100);
        }
      }
    };

    function waitForSubscribeForm() {
      var mainContent = document.getElementById("content-main");
      var target = document.getElementById("footer");
      if (target) {
        tries = 0;
        var fields = mainContent.getElementsByTagName("input");
        for (var index = 0; index < fields.length; index++) {
          var placeholderText = fields[index].getAttribute("placeholder");
          if (placeholderText != null && placeholderText != "") {
            addPlaceholderText(fields[index], placeholderText, {left: 130});
            tries = 0;
          }
        }
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForSubscribeForm, 100);
        }
      }
    };

    function waitForMemberForm() {
      var mainContent = document.getElementById("content-main");
      var target = document.getElementById("footer");
      if (target) {
        tries = 0;
        var fields = mainContent.getElementsByTagName("input");
        for (var index = 0; index < fields.length; index++) {
          var placeholderText = fields[index].getAttribute("placeholder");
          if (placeholderText != null && placeholderText != "") {
            addPlaceholderText(fields[index], placeholderText);
            tries = 0;
          }
        }
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForMemberForm, 100);
        }
      }
    };

    function addListener(element, eventName, handler) {
        if (element.addEventListener) {
          element.addEventListener(eventName, handler, false);
        } else {
          element.attachEvent("on" + eventName, handler);
        }
    };

    // KUDOS: http://diveintohtml5.org/detect.html
    function supportsInputPlaceholder() {
      var i = document.createElement('input');
      return 'placeholder' in i;
    };
    function supportsTextAreaPlaceholder() {
      var i = document.createElement('textarea');
      return 'placeholder' in i;
    };

    function addPlaceholderText(field, text, config) {
      if (supportsInputPlaceholder()    && field.nodeName.toLowerCase() == "input" ||
          supportsTextAreaPlaceholder() && field.nodeName.toLowerCase() == "textarea") return;
      var p = getAncestor(field, "p");
      var placeholder = document.createElement("i");
      placeholder.innerHTML = text;
      placeholder.className = "placeholder";
      field.parentNode.appendChild(placeholder);
      if (p) {
        p.style.position = "relative";
        // KLUDGE: Temporary solution for the subscribe modal window.
        if (config && config.left) {
            placeholder.style.left = config.left + "px";
        } else {
            placeholder.style.left = field.offsetLeft + "px";
        }
      }

      // Handle the case where the field has a value when the page loads.
      if (field.value != null && field.value != "") {
        placeholder.style.display = "none";
      }

      addListener(field, "focus", function() {
        placeholder.style.display = "none";
      });
      addListener(placeholder, "click", function() {
        placeholder.style.display = "none";
      });
      addListener(field, "blur", function() {
        if (field.value == null || field.value == "") {
          placeholder.style.display = "block";
        }
      });
    };
    window.addPlaceholderText = addPlaceholderText;

    initialize();

  })();


  /* =TextManager
  ----------------------------------------------- */
  var TextManager = function(appendTarget, resizeTarget) {

    var SIZE = {
      SMALL   : "11px",
      MEDIUM  : "12px",
      LARGE   : "14px"
    };
    var buttons = [];

    function initialize() {
      if (!appendTarget) return;
      if (!resizeTarget) resizeTarget = appendTarget;

      render();
      var size = Cookie.get("textsize");
      if (size == null) size = SIZE.SMALL;
      resizeText(size);
    };

    function render() {
      var container = document.createElement("div");
      container.className = "text-manager";
      var headline = document.createElement("h3");
      headline.appendChild(document.createTextNode("Font Size"));
      var buttonContainer = document.createElement("ul");
      container.appendChild(headline);
      container.appendChild(buttonContainer);
      appendTarget.appendChild(container);
      for (var prop in SIZE) {
        var button = new TextManagerButton(resizeText, buttonContainer, SIZE[prop], prop.toLowerCase());
        buttons.push(button);
      }
    };

    function resizeText(size) {
      if (size == null || size == "") size = SIZE.SMALL;
      resizeTarget.style.fontSize = size;
      Cookie.set("textsize", size);
      for (var index = 0; index < buttons.length; index++) {
        var nextButton = buttons[index];
        if (nextButton.getSize() == size) {
          nextButton.activate();
        } else {
          nextButton.deactivate();
        }
      }
    };

    initialize();
  };

  var TextManagerButton = function(callback, container, size, label) {
    var element;

    function initialize() {
      element = document.createElement("li");
      element.className = label;
      container.appendChild(element);
      var link = document.createElement("a");
      link.href = "#" + label;
      link.appendChild(document.createTextNode("A"));
      link.style.fontSize = size;
      element.appendChild(link);
      addListener(link, "click", onClick);
    };
    function activate() {
      addClassName(element, "active");
    };
    function deactivate() {
      removeClassName(element,"active");
    };
    function onClick(e) {
      preventDefault(e);
      callback(size);
    };

    initialize();

    return {
      activate  : activate,
      deactivate: deactivate,
      getSize: function() {
        return size;
      }
    };
  };


  /* =Page
  ----------------------------------------------- */

  (function() {

    var tries = 0;

    function initialize() {
      waitForBody();
    };

    function waitForBody() {
      var body = document.getElementsByTagName("body");
      if (body == null || body.length <= 0) {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForBody, 100);
        }
      } else {
        tries = 0;
        waitForFooter();
      }
    };

    function waitForFooter() {
      var target = document.getElementById("footer");
      if (target) {
        tries = 0;

        // Once the footer is available in the DOM, we know that the main content has completely loaded.

        // Make it easy for the user to resize the main content.
        if (document.body.id == "archive-page" ||
            document.body.id == "tag-page" ||
            document.body.id == "newsletter-page") {
          var mainContent = document.getElementById("content-main");
          var resizeTarget = document.getElementById("content");
          new TextManager(mainContent, resizeTarget);
        } else if (document.body.id != "home-page") {
          new TextManager(document.getElementById("content-main"));
        }

        // Set up the email links on the newsletter page as modal windows.
        if (document.body.id == "newsletter-page") {
          setupNewsletterPage();
        }

        // Set up the email links on the podcasts page as modal windows.
        if (document.body.id == "podcasts-page") {
          setupPodcastsPage();
        }

        // Set up the personal note links on the conversations login page as modal windows.
        if (document.body.id == "conversations-page") {
          setupConversationsPage();
        }

        // Set up action link on the over shoulder page, as a modal window.
        if (document.body.id == "over-shoulder-page") {
          setupOverShoulderPage();
        }

        // Set up the subscription links on the profile page as modal windows. Added by Eva 20110202
        if (document.body.id == "profile-page") {
          setupProfilePage();
        }

        if (document.body.id == "over-shoulder-library-page") {
          waitForSubscribeAd();
        }

        // Set up the library links on the conversations library page, so they save the user's place in the scrollable list.
        if (document.body.id == "conversations-library-page" ||
            document.body.id == "podcasts-page") {
          setupConversationsLibraryPage();
        }

        if (isMSIE && version < 8) {
          if (document.body.id == "archive-page" ||
              document.body.id == "tag-page" ||
              document.body.id == "newsletter-page" ||
              document.body.id == "over-shoulder-library-page") {
            simulate();
          }
        }
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForFooter, 100);
        }
      }
    };

    function after(list, content) {
      for (var index = 0; index < list.length; index++) {
        var contentElement = document.createElement("span");
        contentElement.innerHTML = content;
        list[index].appendChild(contentElement);
      };
    };
    function simulate() {
      // Simulate CSS dynamic content, since IE6/7 don't support it.
      var mainContent = document.getElementById("content-main");
      if (mainContent) {
        var newsletters = getAllElementsByClassName(mainContent, "newsletter");
        if (!newsletters || !newsletters.length) newsletters = getAllElementsByClassName(mainContent, "newsletter-list");
        if (newsletters && newsletters.length) {
          for (var index = 0; index < newsletters.length; index++) {
            var newsletter = newsletters[index];
            (function() {
              var tagsElements = getAllElementsByClassName(newsletter, "tags");
              if (tagsElements && tagsElements.length) {
                for (var index = 0; index < tagsElements.length; index++) {
                  var tags = tagsElements[index];
                  var headlines = tags.getElementsByTagName("h3");
                  if (headlines) {
                    after(headlines, " <span style=\"padding-right: 0.25em\">:</span>");
                  }
                  var listItems = tags.getElementsByTagName("a");
                  if (listItems) {
                    listItems = getArray(listItems);
                    listItems.pop();
                    after(listItems, "<span style=\"padding-right: 0.25em\">,</span>");
                  }
                }
              }
              var permalinkElements = getAllElementsByClassName(newsletter, "permalink");
              if (permalinkElements && permalinkElements.length) {
                for (var index = 0; index < permalinkElements.length; index++) {
                  var permalink = permalinkElements[index];
                  var headlines = permalink.getElementsByTagName("h3");
                  if (headlines) {
                    after(headlines, " <span style=\"padding-right: 0.25em\">:</span>");
                  }
                }
              }
            })();
          }
        }
      }
    };

    function setupNewsletterPage() {
      var mainContent = document.getElementById("content-main");
      if (mainContent) {
        var newsletter = getOneElementByClassName(mainContent, "newsletter");
        if (newsletter) {
          var emailButtons = getAllElementsByClassName(newsletter, "email");
          for (var index = 0; index < emailButtons.length; index++) {
            new ModalWindow(emailButtons[index].getElementsByTagName("a")[0], {
              width: 490,
              excludeCloseLink: true
            });
          }
          var printButtons = getAllElementsByClassName(newsletter, "print");
          for (var index = 0; index < printButtons.length; index++) {
            var link = printButtons[index].getElementsByTagName("a")[0];
            addListener(link, "click", function(e) {
              preventDefault(e);
              window.print();
            });
          }
        }
      }
    };

    function setupPodcastsPage() {
      var mainContent = document.getElementById("content-main");
      if (mainContent) {
        var library = getOneElementByClassName(mainContent, "conversations-library");
        if (library) {
          var emailButtons = getAllElementsByClassName(library, "email");
          for (var index = 0; index < emailButtons.length; index++) {
            new ModalWindow(emailButtons[index].getElementsByTagName("a")[0], {
              width: 490,
              excludeCloseLink: true
            });
          }
        }
      }
    };

    function setupConversationsPage() {
      var mainContent = document.getElementById("content-main");
      if (mainContent) {
        var testimonials = getOneElementByClassName(mainContent, "testimonials");
        if (testimonials) {
          var more = getOneElementByClassName(testimonials, "more");
          if (more) {
            new ModalWindow(more.getElementsByTagName("a")[0], {
              width: 560,
              height: 350
            });
          }
        }
        var note = getOneElementByClassName(mainContent, "personal-note");
        if (note) {
          new ModalWindow(note, {
            width: 560,
            height: 475
          });
        }
        setTimeout(function() {
          var modal;
          var subscribeButtons = getAllElementsByClassName(mainContent, "action");
          // Set up the first subscribe button.
          if (subscribeButtons.length > 0) {
            modal = new ModalWindow(subscribeButtons[0].getElementsByTagName("a")[0], {
              width: 400,
              excludeCloseLink: true
            });
          }
          // Set up any additional subscribe buttons on the page.
          for (var index = 1; index < subscribeButtons.length; index++) {
            var link = subscribeButtons[index].getElementsByTagName("a")[0];
            addListener(link, "click", modal.onClick);
          }
        }, 1000);
      }
    };

		function waitForSubscribeAd() {
      var aside = getOneElementByClassName(document.body, "aside");
      var target;
      if (aside) {
        target = getOneElementByClassName(aside, "subscribe");
      }
      if (target) {
        tries = 0;
	      setTimeout(function() {
	        var modal;
	        var subscribeButtons = getAllElementsByClassName(target, "action");
	        // Set up the first subscribe button.
	        if (subscribeButtons.length > 0) {
	          modal = new ModalWindow(subscribeButtons[0].getElementsByTagName("a")[0], {
	            width: 400,
	            excludeCloseLink: true
	          });
	        }
	      }, 1000);
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForSubscribeAd, 100);
        }
      }
		};

    function setupProfilePage() {
      var mainContent = document.getElementById("content-main");
      if (mainContent) {
        setTimeout(function() {
          var subscribeButtons = getAllElementsByClassName(mainContent, "action");
          // Set up the subscribe/cancel buttons.
          for (var index = 0; index < subscribeButtons.length; index++) {
            new ModalWindow(subscribeButtons[index].getElementsByTagName("a")[0], {
              width: 400,
              excludeCloseLink: true
            });
          }
        }, 1000);
      }
    };

    function setupOverShoulderPage() {
      var mainContent = document.getElementById("content-main");
      if (mainContent) {
        setTimeout(function() {
          var modal;
          var subscribeButtons = getAllElementsByClassName(mainContent, "action");
          // Set up the first subscribe button.
          if (subscribeButtons.length > 0) {
            modal = new ModalWindow(subscribeButtons[0].getElementsByTagName("a")[0], {
              width: 400,
              excludeCloseLink: true
            });
          }
          // Set up any additional subscribe buttons on the page.
          for (var index = 1; index < subscribeButtons.length; index++) {
            var link = subscribeButtons[index].getElementsByTagName("a")[0];
            addListener(link, "click", modal.onClick);
          }
        }, 1000);
      }
    }

    function setupConversationsLibraryPage() {
      var mainContent = document.getElementById("content-main");
      if (mainContent) {
        var library = getOneElementByClassName(mainContent, "conversations-library");
        if (library) {
          var inner = getOneElementByClassName(library, "inner");

          // Attempt to restore the window's scroll position, and the library scroll position.
          /*
          try {
            var hash = window.location.hash;
            if (hash && hash.indexOf("#active-conversations-library-item/") == 0) {
              var scrollRequests = hash.replace("#active-conversations-library-item/", "").split("/");
              if (scrollRequests[0] && !isNaN(scrollRequests[0])) {
                 window.scrollTo(null, scrollRequests[0]);
              }
              if (scrollRequests[1] && !isNaN(scrollRequests[1])) {
                inner.scrollTop = scrollRequests[1];
              }
            }
          } catch(e) {
            console.log(e);
          }
          */

          var links = library.getElementsByTagName("a");
          for (var index = 0; index < links.length; index++) {
            (function() {
              var link = links[index];

              // Locate the item that contains the link
              var item = link;
              do {
                item = item.parentNode;
              } while((!item.className || item.className.indexOf("item") < 0) && item.parentNode != null);

              if (item && item.className &&
                  item.className.indexOf("item") >= 0 &&
                  (link.innerHTML.indexOf("Conversation") >= 0 ||
                   link.href.indexOf("podcast") >= 0)) {

                // Make the entire item clickable
                addClassName(item, "clickable");
                addListener(item, "click", function(e) {
                    window.location.href = link.href.split("#")[0] + "#active-conversations-library-item" + "/" + getScrollOffsets().top + "/" + inner.scrollTop;
                });
                addListener(link, "click", function(e) {
                  preventDefault(e);
                });

              }
            })();
          }
        }
      }
    };

    initialize();
  })();


  /* =Scroll Position
  ----------------------------------------------- */
  (function() {

    var tries = 0;

    function initialize() {
      waitForBody();
    };

    function waitForBody() {
      var body = document.getElementsByTagName("body");
      if (body == null || body.length <= 0) {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 500) {
          // Wait 1/10 second, and try again
          setTimeout(waitForBody, 10);
        }
      } else {
        tries = 0;
        // Set up the library links on the conversations library page, so they save the user's place in the scrollable list.
        if (body[0].id == "conversations-library-page" ||
            body[0].id == "podcasts-page") {
          waitForMainContent();
        }
      }
    };

    function waitForMainContent() {
      var target = document.getElementById("content-main");
      if (target) {
        tries = 0;
        waitForConversationsLibrary(target);
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 500) {
          // Wait 1/10 second, and try again
          setTimeout(waitForMainContent, 10);
        }
      }
    };

    function waitForConversationsLibrary(mainContent) {
      var target = getOneElementByClassName(mainContent, "conversations-library");
      if (target) {
        tries = 0;
        waitForConversationsLibraryInner(target);
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 500) {
          // Wait 1/10 second, and try again
          setTimeout(function() {
            waitForConversationsLibrary(mainContent);
          }, 10);
        }
      }
    };

    function waitForConversationsLibraryInner(library) {
      var target = getOneElementByClassName(library, "inner");
      if (target) {
        tries = 0;
        setupConversationsLibrary(target);
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 500) {
          // Wait 1/10 second, and try again
          setTimeout(function() {
            waitForConversationsLibraryInner(library);
          }, 10);
        }
      }
    };

    function setupConversationsLibrary(inner) {
      // Attempt to restore the window's scroll position, and the library scroll position.
      try {
        var hash = window.location.hash;
        if (hash && hash.indexOf("#active-conversations-library-item/") == 0) {
          var scrollRequests = hash.replace("#active-conversations-library-item/", "").split("/");
          if (scrollRequests[0] && !isNaN(scrollRequests[0])) {
             //window.scrollTo(null, scrollRequests[0]);
          }
          if (scrollRequests[1] && !isNaN(scrollRequests[1])) {
            function setScrollTop() {
              inner.scrollTop = Number(scrollRequests[1]);
              // Keep trying to set the scroll top, until it works (for some reason, it doesn't always take at first in Safari.)
              setTimeout(function() {
                if (Number(inner.scrollTop) != Number(scrollRequests[1])) {
                  setScrollTop();
                }
              }, 10);
            }
            setScrollTop();
          }
        }
      } catch(e) {
        console.log(e);
      }
    };

    initialize();

  })();


  /* =Video Gallery
  ----------------------------------------------- */
  var VideoGallery = function(element) {
    if (!element) return;

    var items;
    var activeItem;
    var slideShow;
    var outer;
    var inner;
    var nowPlaying;
    var maxVideoHeight = 0;
    var activeButton;

    function initialize() {

      items = getAllElementsByClassName(element, "item");
      if (!items.length || items.length < 1) return;

      slideShow = document.createElement("div");
      slideShow.className = "slideshow";
      element.appendChild(slideShow);
      outer = document.createElement("div");
      outer.className = "outer";
      slideShow.appendChild(outer);
      inner = document.createElement("div");
      inner.className = "inner";
      outer.appendChild(inner);

      var nowPlaying = document.createElement("h3");
      nowPlaying.className = "now-playing";
      nowPlaying.appendChild(document.createTextNode("Now Playing"));

      // 1) Hide all but the first video.
      // 2) Add a list of videos, where each item is a button that will reveal that video.
      for (var index = 0; index < items.length; index++) {
        /*
        var emailButtons = getAllElementsByClassName(items[index], "email");
        for (var j = 0; j < emailButtons.length; j++) {
          new ModalWindow(emailButtons[j].getElementsByTagName("a")[0], {
            width: 490,
            excludeCloseLink: true
          });
        }
        */
        var video = getOneElementByClassName(items[index], "video");
        if (video.offsetHeight > maxVideoHeight) maxVideoHeight = video.offsetHeight;
        items[index].style.display = "none";
        var headline = items[index].getElementsByTagName("h4")[0];
        headline.parentNode.insertBefore(nowPlaying.cloneNode(true), headline);
        if (!items[index].id) items[index].id = "video-" + index;
        addSlideShowItem(items[index]);
      }

      // Show the first item by default.
      activeItem = items[0];
      activeButton = getOneElementByClassName(slideShow, "item");

      // Show the item indicated in the hash, if one exists
      (function() {
        var hash = window.location.hash;
        if (hash) {
          var id = hash.replace("#", "");
          var item = document.getElementById(id);
          if (item) {
            activeItem = item;
          }
          var button = document.getElementById("button-" + id);
          if (button) {
            activeButton = button;
          }
        }
      })();

      activeItem.style.display = "block";
      activeItem.style.minHeight = maxVideoHeight + "px";
      addClassName(activeButton, "active");


      // 3) Make the list act like a slideshow.
      new SlideShow(slideShow, {
        itemsPerSet: 5,
        horizontal: true,
        autorun: false,
        loop: false,
        calculateSizes: function(items, itemsPerSet) {
          try {
            var itemWidth = 145;
            var setWidth = itemWidth * itemsPerSet;
            var viewportWidth = setWidth - 15;
          } catch(e) {
            console.error(e);
            return;
          }
          return {
            itemSize: itemWidth,
            setSize: setWidth,
            viewportSize: viewportWidth
          };
        }
      });
    };

    function getFormattedDate(datestring) {
      var newDate;
      var date = new Date(datestring);
      newDate = (date.getMonth() + 1) + "." + date.getDate() + "." + String(date.getFullYear()).substring(2);
      return newDate;
    };

    function addSlideShowItem(videoItem) {
      var item = document.createElement("div");
      item.className = "item";
      item.id = "button-" + videoItem.id;
      inner.appendChild(item);

      var button = document.createElement("a");
      button.href = "#" + videoItem.id;
      item.appendChild(button);

      var image = videoItem.getElementsByTagName("img")[0];
      image.parentNode.removeChild(image);
      button.appendChild(image);

      var headline = videoItem.getElementsByTagName("h4")[0];
      var newHeadline = document.createElement("span");
      newHeadline.className = "headline";
      newHeadline.innerHTML = headline.innerHTML;
      // If a short headline has been specified, use that instead.
      var shortdesc = videoItem.getAttribute("shortdesc");
      if (shortdesc) newHeadline.innerHTML = shortdesc;
      button.appendChild(newHeadline);

      button.appendChild(document.createTextNode(" "));

      try {
        var date = getOneElementByClassName(videoItem, "date");
        var newDate = document.createElement("span");
        newDate.className = "date";
        newDate.innerHTML = getFormattedDate(date.innerHTML);
        button.appendChild(newDate);
      } catch(e) {}

      button.appendChild(document.createTextNode(" | "));

      try {
        var duration = getOneElementByClassName(videoItem, "duration");
        var newDuration = document.createElement("span");
        newDuration.className = "duration";
        newDuration.innerHTML = duration.innerHTML;
        button.appendChild(newDuration);
      } catch(e) {}

      function onClick(e) {
        preventDefault(e);
        activeItem.style.display = "none";
        if (activeButton) removeClassName(activeButton, "active");
        activeItem = document.getElementById(videoItem.id);
        activeItem.style.display = "block";
        activeItem.style.minHeight = maxVideoHeight + "px";
        activeButton = item;
        addClassName(activeButton, "active");
        //window.location.href = window.location.href.split("#")[1] + "#" + videoItem.id;
      }

      addListener(button, "click", onClick);
    };

    initialize();

    return {
      getHeight: function() {
        if (!activeItem) return;
        return activeItem.offsetHeight;
      }
    };
  };

  (function() {

    var tries = 0;
    var spinner;
    var spinnerContainer;
    var videos;

    function initialize() {
      waitForBody();
    };

    function waitForBody() {
      var body = document.getElementsByTagName("body");
      if (body == null || body.length <= 0) {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForBody, 100);
        }
      } else {
        tries = 0;
        if (body[0].id == "videos-page") {
          waitForMainContent();
          waitForFooter();
        }
      }
    };

    function waitForMainContent() {
      var target = document.getElementById("content-main");
      if (target) {
        tries = 0;
        spinnerContainer = document.createElement("div");
        spinnerContainer.className = "spinner";
        target.appendChild(spinnerContainer);
        spinner = new Spinner(spinnerContainer, {size: 60});
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForMainContent, 100);
        }
      }
    };

    function waitForFooter() {
      var target = document.getElementById("footer");
      if (target) {
        tries = 0;

        // Once the footer is available in the DOM, we know that the main content has completely loaded.
        var mainContent = document.getElementById("content-main");
        videos = getOneElementByClassName(mainContent, "videos");
        var gallery = new VideoGallery(videos);
        var height = gallery.getHeight() + 19;
        var nav = getOneElementByClassName(videos, "nav");
        nav.style.top = String(height) + "px";
        var navSub = getOneElementByClassName(videos, "nav-sub");
        navSub.style.top = String(height) + "px";

        onLoaded();
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForFooter, 100);
        } else {
          onLoaded();
        }
      }
    };

    function onLoaded() {
      // Indicate to the CSS that the videos have loaded.
      if (spinner && spinner.stop) spinner.stop();
      if (spinnerContainer) spinnerContainer.parentNode.removeChild(spinnerContainer);
      addClassName(videos, "loaded");
    };

    initialize();

  })();


  /* =Photo Gallery
  ----------------------------------------------- */
  var PhotoGallery = function(element) {
    if (!element) return;

    var album;
    var items;
    var itemsLength;
    var buttons = [];
    var cursor = 0;
    var grid;
    var gridSlideShow;
    var outer;
    var inner;
    var nextButton;
    var previousButton;
    var maxPhotoHeight = 0;

    var animationTimer;
    var duration = 0.15;
    var intervalTime = 50;
    var numIntervals = duration * 1000 / intervalTime;
    var depth = 0;

    function initialize() {

      if (!album) album = getOneElementByClassName(element, "album");
      if (!items) items = album.getElementsByTagName("li");

      // If the last image hasn't loaded yet, wait 1/10 second and try again.
      var lastImage = items[items.length - 1].getElementsByTagName("img")[0];
      if (lastImage.offsetWidth == 0) {
        setTimeout(initialize, 100);
        return;
      }

      itemsLength = items.length; // The number of <li /> items may change, since getElementsByTagName
                                  // returns a dynamic list. But we only care about the initial number.

      grid = document.createElement("div");
      grid.className = "grid";
      album.appendChild(grid);
      outer = document.createElement("div");
      outer.className = "outer";
      grid.appendChild(outer);
      inner = document.createElement("div");
      inner.className = "inner";
      outer.appendChild(inner);

      var nowViewing = document.createElement("h3");
      nowViewing.className = "now-viewing";
      nowViewing.appendChild(document.createTextNode("Now Viewing"));
      var headline = album.getElementsByTagName("h2")[0];
      headline.parentNode.insertBefore(nowViewing, headline);

      // 1) Hide all but the first photo.
      // 2) Add a grid of photos, where each item is a button that will reveal that photo.
      for (var index = 0; index < itemsLength; index++) {
        //items[index].style.display = "none";
        if (items[index].offsetHeight > maxPhotoHeight) maxPhotoHeight = items[index].offsetHeight;
        if (!items[index].id) items[index].id = "photo-" + index;
        addGridButton(items[index], index);
      }
      activeButton = getOneElementByClassName(grid, "item");

      // TODO: Show the item indicated in the hash, if one exists
      // Otherwise, show the first item.
      items[0].style.zIndex = "1";
      items[0].style.minHeight = maxPhotoHeight + "px";
      album.style.minHeight = maxPhotoHeight + "px";
      addClassName(buttons[0], "active");

      render();
      updateButtonState();
      addListener(nextButton, "click", onNextClick);
      addListener(previousButton, "click", onPreviousClick);

      // 3) Make the grid act like a slideshow.
      if (itemsLength >= 5) {
        gridSlideShow = new SlideShow(grid, {
          itemsPerSet: 16,
          rowsPerSet: 4,
          horizontal: true,
          autorun: false,
          loop: false,
          calculateSizes: function(items, itemsPerSet) {
            try {
              var itemWidth = 72;
              var setWidth = itemWidth * itemsPerSet;
              var viewportWidth = setWidth - 2;
            } catch(e) {
              console.error(e);
              return;
            }
            return {
              itemSize: itemWidth,
              setSize: setWidth,
              viewportSize: viewportWidth
            };
          }
        });
      // If there aren't enough items to make a slideshow, arrange them nicely instead.
      } else {
        var gridItems = getAllElementsByClassName(grid, "item");
        for (var index = 0; index < gridItems.length; index++) {
          gridItems[index].style.position = "relative";
          gridItems[index].style.cssFloat = "left";
          gridItems[index].style.styleFloat = "left"; // For IE
        }
        gridItems[gridItems.length - 1].style.marginRight = "0";
      }

      (function() {
        var nav = getOneElementByClassName(element, "nav");
        var list = nav.getElementsByTagName("ul")[0];
        list.className = "inner";
        list.parentNode.removeChild(list);
        var slideshow =  document.createElement("div");
        slideshow.className = "slideshow";
        nav.appendChild(slideshow);
        var outer = document.createElement("div");
        outer.className = "outer";
        slideshow.appendChild(outer);
        outer.appendChild(list);
        nav.parentNode.removeChild(nav);
        element.appendChild(nav);
        var items = list.getElementsByTagName("li");
        for (var index = 0; index < items.length; index++) {
          addClassName(items[index], "item");
        }

        new SlideShow(nav, {
          itemsPerSet: 5,
          horizontal: true,
          autorun: false,
          loop: false,
          calculateSizes: function(items, itemsPerSet) {
            try {
              var itemWidth = 145;
              var setWidth = itemWidth * itemsPerSet;
              var viewportWidth = setWidth - 15;
            } catch(e) {
              console.error(e);
              return;
            }
            return {
              itemSize: itemWidth,
              setSize: setWidth,
              viewportSize: viewportWidth
            };
          }
        });
      })();
    };

    function render() {
      var list = document.createElement("div");
      album.appendChild(list);
      list.className = "nav-item";

      var item;

      item = document.createElement("div");
      item.className = "next";
      list.appendChild(item);

      nextButton = document.createElement("a");
      nextButton.href = "#next";
      nextButton.appendChild(document.createTextNode("Next"));
      item.appendChild(nextButton);

      item = document.createElement("div");
      item.className = "previous";
      list.appendChild(item);

      previousButton = document.createElement("a");
      previousButton.href = "#next";
      previousButton.appendChild(document.createTextNode("Previous"));
      item.appendChild(previousButton);
    };

    function addGridButton(photoItem, index) {
      var div = document.createElement("div");
      div.className = "item";
      inner.appendChild(div);
      buttons.push(div);

      var button = document.createElement("a");
      button.href = "#" + photoItem.id;
      div.appendChild(button);

      var bigImage = photoItem.getElementsByTagName("img")[0];
      var image = photoItem.getElementsByTagName("img")[0].cloneNode(true);
      var originalHeight = image.height;
      image.height = 53;
      image.width = Math.round(bigImage.offsetWidth * 0.175);
      if (image.width > 70) image.width = 70;
      var background = document.createElement("div");
      background.className = "image";
      button.appendChild(background);
      background.appendChild(image);

      var border = document.createElement("div");
      border.className = "border";
      button.appendChild(border);

      function onClick(e) {
        preventDefault(e);
        setActiveItem(index);
        //window.location.href = window.location.href.split("#")[1] + "#" + photoItem.id;
      }

      addListener(button, "click", onClick);
    };

    function setActiveItem(index) {
      // Reset the active item & button
      //items[cursor].style.display = "none";
      removeClassName(buttons[cursor], "active");

      cursor = index;
      //items[cursor].style.display = "block";
      items[cursor].style.minHeight = maxPhotoHeight + "px";
      album.style.minHeight = maxPhotoHeight + "px";
      addClassName(buttons[cursor], "active");
      updateButtonState();

      var item = items[cursor];
      item.style.opacity = 0;
      item.style.zIndex = ++depth;

      if (animationTimer) clearInterval(animationTimer);
      animationTimer = setInterval(function() {
        var current = Number(item.style.opacity);
        var interval = (1 - current) / numIntervals;
        var opacity = current + interval;
        if ((1 - opacity) <= 0.05) {
          opacity = 1;
          if (animationTimer) clearInterval(animationTimer);
        }
        item.style.opacity = opacity;
      }, intervalTime);
    };

    function onNextClick(e) {
      preventDefault(e);
      next();
    };

    function onPreviousClick(e) {
      preventDefault(e);
      previous();
    };

    function next() {
      if (atEnd()) return;
      setActiveItem(cursor + 1);
    };

    function previous() {
      if (atStart()) return;
      setActiveItem(cursor - 1);
    };

    function updateButtonState() {
      if (!nextButton || !previousButton) return;
      if (atEnd()) {
        addClassName(nextButton, "disabled");
      } else {
        removeClassName(nextButton, "disabled");
      }
      if (atStart()) {
        addClassName(previousButton, "disabled");
      } else {
        removeClassName(previousButton, "disabled");
      }

      // Scroll the grid until the current button is within the viewport.
      if (gridSlideShow) gridSlideShow.scrollTo(cursor);
    };

    function atStart() {
      return cursor == 0;
    };

    function atEnd() {
      return cursor >= itemsLength - 1;
    };

    initialize();
  };

  (function() {

    var tries = 0;
    var spinner;
    var spinnerContainer;
    var photos;

    function initialize() {
      waitForBody();
    };

    function waitForBody() {
      var body = document.getElementsByTagName("body");
      if (body == null || body.length <= 0) {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForBody, 100);
        }
      } else {
        tries = 0;
        if (body[0].id == "photos-page") {
          waitForMainContent();
          waitForFooter();
        }
      }
    };

    function waitForMainContent() {
      var target = document.getElementById("content-main");
      if (target) {
        tries = 0;
        spinnerContainer = document.createElement("div");
        spinnerContainer.className = "spinner";
        target.appendChild(spinnerContainer);
        spinner = new Spinner(spinnerContainer, {size: 60});
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForMainContent, 100);
        }
      }
    };

    function waitForFooter() {
      var target = document.getElementById("footer");
      if (target) {
        tries = 0;

        setTimeout(function() {
          // Once the footer is available in the DOM, we know that the main content has completely loaded.
          var mainContent = document.getElementById("content-main");
          photos = getOneElementByClassName(mainContent, "photos");
          new PhotoGallery(photos);

          onLoaded();
        }, 100);
      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForFooter, 100);
        } else {
          onLoaded();
        }
      }
    };

    function onLoaded() {
      // Indicate to the CSS that the photos have loaded.
      if (spinner && spinner.stop) spinner.stop();
      if (spinnerContainer) spinnerContainer.parentNode.removeChild(spinnerContainer);
      addClassName(photos, "loaded");
    };

    initialize();

  })();


  /* =Calendadr
  ----------------------------------------------- */
  var CalendarToolTip = function(element) {
    if (!element) return;

    var button;
    var details;

    function initialize() {
      button = getOneElementByClassName(element, "link");
      details = getOneElementByClassName(element, "calendar-event-details");
      if (!button || !details) return;

      details.parentNode.removeChild(details);
      document.body.appendChild(details);
      details.style.position = "absolute";
      details.style.zIndex = "9999";
      details.style.width = "250px";
      details.style.top = "0";
      details.style.left = "-9999px";

      addListener(button, "mouseover", function(e) {
        var viewportDimensions = getViewportDimensions();
        var scroll = getScrollOffsets();
        var top = e.clientY + scroll.top - (details.offsetHeight + 15);
        var left = e.clientX + scroll.left;
        // TBD: This works, but it can obscure the button.
        //if (top < scroll.top + 50) top = scroll.top + 50;
        if (left + details.offsetWidth - 50 > viewportDimensions.width) left = scroll.left + viewportDimensions.width - details.offsetWidth - 50;
        details.style.top = top + "px";
        details.style.left = left + "px";
      });
      addListener(button, "mouseout", function(e) {
        details.style.top = "0";
        details.style.left = "-9999px";
      });
    };

    initialize();
  };

  (function() {

    var tries = 0;

    function initialize() {
      waitForBody();
    };

    function waitForBody() {
      var body = document.getElementsByTagName("body");
      if (body == null || body.length <= 0) {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForBody, 100);
        }
      } else {
        tries = 0;
        if (body[0].id == "calendar-page") {
          waitForFooter();
        }
      }
    };

    function waitForFooter() {
      var target = document.getElementById("footer");
      if (target) {
        tries = 0;

        var calendar = document.getElementById("fc_calendar");
        var events = getAllElementsByClassName(calendar, "event");
        for (var index = 0; index < events.length; index++) {
          new CalendarToolTip(events[index]);
        }

      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForFooter, 100);
        }
      }
    };

    initialize();

  })();


  (function() {

    var tries = 0;

    function initialize() {
      waitForFooter();
    };

    function waitForFooter() {
      var target = document.getElementById("footer");
      if (target) {
        tries = 0;

        var content = document.getElementById("content");
        if (!content) return;

        var forms = content.getElementsByTagName("form");
        for (var index = 0; index < forms.length; index++) {
          new ValidateForm(forms[index]);
        }

      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForFooter, 100);
        }
      }
    };

    initialize();

  })();


  /* =Popup Window
  -----------------------------------------------
  Open the "subscribe to email list" button in a new window on "over my shoulder" pages.
  ----------------------------------------------- */
  /*
  (function() {

    var tries = 0;

    function initialize() {
      waitForFooter();
    };

    function waitForFooter() {
      var target = document.getElementById("footer");
      if (target) {
        tries = 0;

        if (document.body.id != "over-shoulder-library-page") return;

        var content = document.getElementById("content");
        if (!content) return;

        var aside = getOneElementByClassName(content, "aside");
        if (!aside) return;

        var connect = getOneElementByClassName(aside, "connect");
        if (!connect) return;

        var email = getOneElementByClassName(content, "email");
        if (!email) return;

        var links = email.getElementsByTagName("a");
        if (!links || links.length <= 0) return;

        var link = links[0];
        addListener(link, "click", function(e) {
            preventDefault(e);
            var viewportDimensions = getViewportDimensions();
            var width = 550;
            var height = 520;
            window.open(link.href, "mailinglist", "location=1,status=1,scrollbars=1,width=" + width + ",height=" + height + ",left=" +
                (window.screenLeft + Math.round((viewportDimensions.width - height) / 2)) + ",top=" +
                (window.screenTop + Math.round((viewportDimensions.height - width) / 2)));
        });

      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForFooter, 100);
        }
      }
    };

    initialize();

  })();
  */

};

if (isMSIE && version < 8) {

  // Wait for the page to load in IE 6/7, to avoid an "operation aborted" error.
  (function() {

    var tries = 0;

    function initialize() {
      var temp = window.onload;
      window.onload = function() {
        if (typeof(temp) == "function") temp();

        // An inline script hides the <body /> while the JavaScript loads.
        if (document.body) document.body.style.visibility = "visible";

        main();
      };
      waitForFooter();
    };

    function waitForFooter() {
      var target = document.getElementById("connect");
      if (target) {
        tries = 0;

        // An inline script hides the <body /> while the JavaScript loads.
        if (document.body) document.body.style.visibility = "visible";

        main();

      } else {
        // If we haven't been trying for more than 5 seconds
        if (tries++ < 50) {
          // Wait 1/10 second, and try again
          setTimeout(waitForFooter, 100);
        }
      }
    };

    initialize();

  })();

} else {
  main();
}

})(); // END: Sandbox


