
// script utilities
var Script = Script || (function(GLOBALOBJECT) {

    // monitor and respond to document load
    var isLoaded = false;
    var loadActions = [];
    (function() {
        var f = function() {
            if (!isLoaded) {
                isLoaded = true;
                // run actions currently pending document load
                var i;
                for (i = 0; i < loadActions.length; i++) {
                    loadActions[i].run();
                }
                loadActions.length = 0;
            }
        };
        if (window.addEventListener) {
            window.addEventListener("load", f, false);
        } else if (window.attachEvent) {
            window.attachEvent("onload", f);
        } else {
            var onload = window.onload;
            window.onload = function(event) {
                onload.apply(window, [event || window.event]);
                f.apply(window, []);
            };
        }
    })();

    // test definitions
    var defined = function(dotname) {
        var obj = GLOBALOBJECT;
        var parts = dotname.split(".");
        for (var i = 0; i < parts.length; i++) {
            var t = typeof obj;
            if (t != "object" && t != "function"
                    || obj === null
                    || obj[parts[i]] === undefined) {
                return null;
            }
            obj = obj[parts[i]];
        }
        return obj;
    }; // defined
    
    // script actions waiting for required definitions
    var reqsActions = [];
    var reqsUpdate = function() {
        // try again to run pending scripts
        var acts = reqsActions;
        reqsActions = [];
        var i;
        for (i = 0; i < acts.length; i++) {
            acts[i].run();
        };
    };
    
    // deal with "_nocache=..." parameter
    var addNocache = function(url) {
        if (window.location.search.match(/[?&]jsdebug\.nocacheURL=off/) == null 
                && url.match(/([?&])_nocache=\d+($|&)/) == null) {
            // add nocache
            if (url.indexOf("?") == -1) {
                url += "?";
            } else {
                url += "&";
            }
            url += "_nocache=" + escape(Math.floor(Math.random() * 2147483648));
        }
        return url;
    };
    var removeNocache = function(url) {
        var result = url.replace(/([?&])_nocache=\d+($|&)/g, "$1$2");
        if (result != url) {
            result = result.replace(/[?&]$/g, "");
            result = result.replace(/([?&])&/g, "$1");
        }
        return result;
    };
    
    // matching script element
    var findScript = function(url) {
        // absolutize the script URL
        var temp = document.createElement("SCRIPT");
        temp.src = removeNocache(url);
        url = temp.src;
        // look for a matching script element
        var elts = document.getElementsByTagName("SCRIPT");
        var i;
        for (i = 0; i < elts.length; i++) {
            if (removeNocache(elts[i].src) == url) {
                return elts[i];
            }
        }
        return null;
    }; // findScript
    
    // unconditionally inject javascript from the given location
    var inject = function(url, replace) {
        var elt = document.createElement("SCRIPT");
        elt.type = "text/javascript";
        elt.src = addNocache(url);
        if (replace == null || replace.parentNode == null) {
            document.getElementsByTagName("HEAD")[0].appendChild(elt);
        } else {
            replace.parentNode.replaceChild(elt, replace);
        }
    }; // inject
    
    // create the Script object
    return {
        
        // global object reference
        getGlobalObject: function() {
            return GLOBALOBJECT;
        }, // getGlobalObject
        
        defined: defined,
        
        inject: function(url) {
            var elt = findScript(url);
            inject(url, elt);
        }, // inject
        
        injectAll: function(urlList) {
            var i;
            for (i = 0; i < urlList.length; i++) {
                Script.inject(urlList[i]);
            }
        }, // injectAll
        
        include: function(url) {
            if (findScript(url) == null) {
                // not included yet
                inject(url, null);
            }
        }, // include
        
        // prepare a namespace
        namespace: function(dotname) {
            var attempt = "";
            var obj = GLOBALOBJECT;
            var parts = dotname.split(".");
            for (var i = 0; i < parts.length; i++) {
                if (attempt != "") {
                    attempt += ".";
                }
                attempt += parts[i];
                if (!obj[parts[i]]) {
                    obj[parts[i]] = {};
                } else if (typeof obj[parts[i]] != "object") {
                    throw new Error("Non-object \"" + attempt + "\" already exists.");
                }
                obj = obj[parts[i]];
            }
            return obj;
        }, // namespace
        
        extractScriptsFromHtml: function(html) {
            var regexp = new RegExp("<script[^>]*>(.|\n)*?<\/script>");
            var scripts = [];
            var match;
            while (true) {
                match = regexp.exec(html);
                if (match == null) {
                    break;
                }
                html = html.substring(0, match.index) + html.substring(match.index + match[0].length);
                match = new RegExp("<script([^>]*)").exec(match[0]);
                match = new RegExp("src=\"([^\"]*)\"").exec(match[1]);
                if (match != null) {
                    scripts.push(match[1]);
                }
            }
            // result object
            return {
                html: html,
                scripts: scripts
            };
        },
        
        // broadcast script definition changes
        update: function() {
            reqsUpdate();
        },
        
        // non-event driven action
        Action: function(func){
            var self = this;
            var reqs = {};
            this.requires = function() {
                var i;
                for (i = 0; i < arguments.length; i++) {
                    reqs[arguments[i]] = true;
                }
                return this;
            };
            this.run = function() {
                if (!isLoaded) {
                    // wait for the document to load
                    loadActions.push(self);
                    return;
                }
                // test for required definitions
                var dotname;
                for (dotname in reqs) {
                    if (!defined(dotname)) {
                        // wait for required definition
                        reqsActions.push(self);
                        return;
                    }
                }
                // schedule the action
                setTimeout(function() {
                    func.apply(GLOBALOBJECT, []);
                }, 0);
            };
        } // Action
    
    }; // Script
    
})(this);

// reference to the global object
// @deprecated replaced with Script.getGlobalObject()
Script.GLOBALOBJECT = Script.getGlobalObject();

/**
 * HOT BUTTON ISSUES: EXPAND BOX
 */ 
function OpenVote(vote) { 
	window.setTimeout(function(){ document.getElementById('hotButtonDrawer'+vote).style.visibility = 'visible'; }, 100);
	window.setTimeout(function(){ document.getElementById('hotButtonDrawer'+vote).style.display = 'block'; }, 100);
	new Effect.SlideDown(document.getElementById('hotButtonDrawer'+vote), {duration:1}); 
	new Effect.Fade(document.getElementById('vote'+vote), {to:.01, duration:.5}); 
	//window.setTimeout(function(){ CloseVote(vote); }, 12000);
	}


/**
 * HOT BUTTON ISSUES: CLOSE BOX
 */
function CloseVote(vote) { 
	new Effect.SlideUp(document.getElementById('hotButtonDrawer'+vote), {duration:1}); 
	new Effect.Appear(document.getElementById('vote'+vote), {duration:2}); 
	}



/* Clear Forms */
function clearText(thefield) {
	if (thefield.defaultValue == thefield.value) {
		thefield.value = ""
		thefield.style.color = '#333333'; 
	}
} 

/* Change Color */
function css_color_change(clas, val)
{
     var all     = document.getElementsByTagName('*');
     var id_only = [];
     for(var i=0; i<all.length; ++i)
     {
          if(all[i].className == clas)
          {
               id_only.push(all[i]);
          }
     }
     for(var j=0; j<id_only.length; ++j)
     {
          var e = id_only[j];
          e.style.color = "#"+val;
     }
}

/* Stuff for sliding */
      var slideIntervalId = null;
      
      function startSlide(caller, id, bottom)
      {
            if(bottom == -20)
            	document.getElementById('mask').style.zIndex = 1;


            clearInterval(slideIntervalId);

            //document.getElementById(id).innerHTML = caller.innerHTML;

            slideIntervalId = setInterval("slide('" + id + "', " + bottom + ");", 1);
			
			if(bottom != -20)
			  setTimeout("document.getElementById('mask').style.zIndex = -1",2600);
      }

      function slide(id, bottom)
      {
            var element = document.getElementById(id);

            var nextSize;

            if(parseInt(element.style.bottom) == bottom)
            {
                  clearInterval(slideIntervalId);
                  return;
            }
            else if(parseInt(element.style.bottom) > bottom)
            {
                  nextSize = parseInt(element.style.bottom) - 2;
            }
            else
            {
                  nextSize = parseInt(element.style.bottom) + 2;
            }

            element.style.bottom = nextSize + "px";
            
           
              
      }

	function select_all()
	{
		
		var text_val=eval("document.form1.tb1");
		text_val.focus();
		text_val.select();
	}

	function disable_m3(){  //disable mousewheel - replaced with return scroll to 0,0 - which stutters but works better
		document.onmousewheel=stop;
	}
	function stop(){
		return false;
	}
function checkSubmit() { // currently not working.  the concept is to set the x value so that a user can just hit return to submit after finishing a text entry
   if(!window.event) { return; }
   if(window.event.keyCode == 13) { 
	document.forms[0].x.value = "33";//just needs a number
	document.forms[0].submit();
	
	 }
   }
   
 function referer(){
 	try{
		document.forms[0].referer.value = parent.document.getElementById('referer').innerHTML;
	}catch(err)
	{
	}
}


// add a trim() method for strings
if (!String.prototype.trim) {
    String.prototype.trim = function() {
        return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
    };
}


// miscellaneous ajax utilities
var AjaxUtil = {
 

    // creates a browser-specific ajax request.
    newRequest: function() {
        var request = null;
        try {
            // everything except microsoft
            request = new XMLHttpRequest();
        } catch (e) {
            try {
                // microsoft
                request = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                try {
                    // other microsoft
                    request = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e) {
                    // something really cool I don't even know about...
                }
            }
        }    
        return request;
    } // newRequest


}; // AjaxUtil


// miscellaneous email-related utilities
var EmailUtil = {
        

    // email address regular expression
    ADDRESS_PATTERN: /^[a-zA-Z0-9_-][a-zA-Z0-9._+-]*@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/,
        
    
    // checks the format of an email address
    isValidAddress: function(email) {
        return this.ADDRESS_PATTERN.test(email);
    }, // isValidAddress


    // parses a delimited string list of valid email address.
    // if the strict flag is false, invalid email addresses are skipped. if
    // the strict flag is true, any invalid email address will cause an empty
    // array to be returned.
    parseAddressList: function(list, delim, strict) {
        var emails = [];
        var parts = list.split(delim);
        var i;
        for (i = 0; i < parts.length; i++) {
            var p = parts[i].trim();
            if (p != "") {
                if (EmailUtil.isValidAddress(p)) {
                    // valid address
                    emails.push(p);
                } else if (strict) {
                    // ignore entire list due to one bad address
                    emails = [];
                    break;
                }
            }
        }
        return emails;
    } // parseAddressList


}; // EmailUtil


// DOM 2 and IE event handler support
var Events = {
        
    isIE: function() {
        return !!document.attachEvent;
    },
        
    addListener: function(obj, type, func, capture) {
        // NYI standardized event object...
        // standardize invocation
        var f = func["com.civicscience.Events._handlerFunction"];
        if (!f) {
            var f = function(e) {
                func.apply(obj, [e || window.event]);
            };
            func["com.civicscience.Events._handlerFunction"] = f;
        }
        if (obj.addEventListener) {
            // DOM 2 events
            obj.addEventListener(type, f, capture);
        } else if (obj.attachEvent) {
            // IE events
            obj.attachEvent("on" + type, f);
        } else {
            // DOM 0 events (really?)
            EventHandling.addHandler(obj, type, func);
        }
    }, // addListener
        
    removeListener: function(obj, type, func, capture) {
        // NYI standardized event object...
        // standardize invocation
        var f = func["com.civicscience.Events._handlerFunction"];
        if (!f) {
            // not registered through the Events class
        } else if (obj.removeEventListener) {
            // DOM 2 events
            obj.removeEventListener(type, f, capture);
        } else if (obj.detachEvent) {
            // IE events
            obj.detachEvent("on" + type, f);
        } else {
            // DOM 0 events (really?)
        }
    }, // removeListener
    
    stopEvent: function(event) {
        if (event.stopPropagation) {
            // DOM 2
            event.stopPropagation();
        } else {
            // IE
            event.cancelBubble = true;
        }
    }, // stopEvent
    
    preventEventDefault: function(event) {
        if (event.preventDefault) {
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    } // preventEventDefault
    
}; // Events


// event handler utilities
var EventHandling = {

    
    // adds an event handler to an object
    addHandler: function(obj, type, func) {
        var prop = "on" + type;
        var orig = obj[prop];
        var f = null;
        obj[prop] = function(event) {
            if (!event) {
                event = window.event;
            }
            if (typeof orig == "function") {
                orig.apply(obj, [event]);
            }
            func.apply(obj, [event]);
        };
    }, // addHandler
    
    // removes an event handler to an object
    removeHandler: function(obj, type) {
        var prop = "on" + type;
        var orig = obj[prop];
        if (typeof orig == "function") {
            obj[prop] = null;
        } 
    }, // addHandler


    // schedules the function for execution when the page loads or immediately
    // if the page has already loaded.
    executeOnLoad: function(func) {
        new Script.Action(func).run();
    }, // executeOnLoad
    
    
    fireSyntheticEvent: function(obj, type) {
        // NYI event propagation
        var func = obj["on" + type];
        if (typeof func == "function") {
            setTimeout(function() { func.apply(obj, []); }, 0);
        }
    } // fireSyntheticEvent
    
        
}; // EventHandling


// miscellaneous DOM utilities
var DOMUtil = {

    
    // adds a class to the element's class name list
    addClass: function(element, clazz) {
        if (typeof element.className == "string") {
            var classes = element.className.split(/\s+/);
            var i = 0;
            for ( ; i < classes.length && classes[i] != clazz; i++);
            if (i == classes.length) {
                element.className += " " + clazz;
                return true;
            }
        }
        return false;
    }, // addClass
    
    
    // removes a class from the element's class name list
    removeClass: function(element, clazz) {
        var didRemove = false;
        if (typeof element.className == "string") {
            var classes = element.className.split(/\s+/);
            var i = 0;
            while (i < classes.length) {
                if (classes[i] == clazz) {
                    classes.splice(i, 1);
                    didRemove = true;
                } else {
                    i++;
                }
            }
            element.className = classes.join(" ");
        }
        return didRemove;
    }, // removeClass
    
    
    // tests whether the element's class name list includes a supplied class
    hasClass: function(element, clazz) {
        if (typeof element.className == "string") {
            var classes = element.className.split(/\s+/);
            for (var i = 0; i < classes.length; i++) {
                if (classes[i] == clazz) {
                    return true;
                }
            }
        }
        return false;
    },
    
    
    matchClass: function(element, regexp) {
        if (typeof element.className == "string") {
            var classes = element.className.split(/\s+/);
            for (var i = 0; i < classes.length; i++) {
                var match = classes[i].match(regexp);
                if (match != null) {
                    return match;
                }
            }
        }
        return null;
    }, // matchClass
    
    
    getAttributeNS: function(element, namespaceURI, localName) {
        if (document.documentElement.namespaceURI) {
            // try browser namespace support
            var value = element.getAttributeNS(namespaceURI, localName);
            if (value) {
                return value;
            }
        }
        // no built-in support for namespace.
        // search elements with matching local names.
        for (var i = 0; i < element.attributes.length; i++) {
            var attr = element.attributes[i];
            var local = attr.nodeName;
            var colon = local.indexOf(":");
            if (colon != -1) {
                local = local.substr(colon + 1, local.length - colon);
            }
            if (local.toLowerCase() == localName.toLowerCase()) {
                // check for a namespace match
                var uri = DOMUtil.getNamespaceURI(element, attr);
                if (uri == namespaceURI) {
                    // found it
                    return attr.nodeValue;
                }
            }
        }
        // no luck
        return "";
    }, // getAttributeNS
    
    
    getElementsByTagNameNS: function(element, namespaceURI, localName) {
        var stack = [element];
        var results = [];
        while (stack.length != 0) {
            var children = stack.pop().childNodes;
            for (var i = 0; i < children.length; i++) {
                if (children[i].nodeType == 1
                        && DOMUtil.getLocalName(children[i]).toLowerCase() == localName.toLowerCase()
                        && DOMUtil.getNamespaceURI(children[i]) == namespaceURI) {
                    // found a match
                    results.push(children[i]);
                }
                // visit the children of this node later
                stack.push(children[i]);
            }
        }
        return results;
    }, // getElementsByTagNameNS
    
    
    getLocalName: function(node) {
        if (node.nodeType == 1 || node.nodeType == 2) {
            // element or attribute
            var name = node.nodeName;
            var colon = name.indexOf(":");
            if (colon == -1) {
                // no prefix
                return name;
            } else {
                // remove the prefix
                return name.substr(colon + 1, name.length - colon);
            }
        } else {
            return undefined;
        }
    }, // getLocalName
    
    
    getNamespaceURI: function(element, attr) {
        var node = attr || element;
        var colon = node.nodeName.indexOf(":");
        var xmlns = "xmlns";
        if (colon > 0) {
            xmlns += ":" + node.nodeName.substr(0, colon).toLowerCase();
        }
        var nsURI = "";
        while (element && element.nodeType == 1) {
            var uri = null;
            for (var i = 0; i < element.attributes.length && !uri; i++) {
                if (element.attributes[i].nodeName.toLowerCase() == xmlns) {
                    uri = element.attributes[i].nodeValue;
                }
            }
            if (uri) {
                nsURI = uri;
                break;
            }
            element = element.parentNode;
        }
        return nsURI;
    }, // getNamespaceURI
    
    
    getElementsByClassName: function(element, clazz, limit) {
        var results = [];
        var stack = [element];
        while (stack.length != 0) {
            element = stack.pop();
            for (var i = 0; i < element.childNodes.length; i++) {
                var child = element.childNodes[i];
                if (child.nodeType == 1) {
                    if (this.hasClass(child, clazz)) {
                        // found a match
                        results.push(child);
                        if (limit != undefined && results.length == limit) {
                            return results;
                        }
                    }
                    stack.push(child);
                }
            }
        }
        return results;
    }, // getElementsByClassName
    
    
    getElementByID: function(id, rootNode) {
        if (!rootNode) {
            rootNode = document;
        }
        if (rootNode.all) {
            // optimization for slow IE DOM method
            var element = rootNode.all[id];
            if (element.id == id) {
                return element;
            } else {
                return undefined;
            }
        } else {
            return document.getElementById(id);
        }
    }, // getElementByID
    
    
    getTextContent: function(element) {
        return element.textContent || element.innerText;
    }, // getTextContent
    
    removeChildren: function(parent) {
        while (parent.hasChildNodes()) {
            parent.removeChild(parent.firstChild);
        }
    }, // removeChildren
    
    swap: function(n1, n2) {
        var pn1 = n1.parentNode;
        var pn2 = n2.parentNode;
        if (pn1 == null && pn2 == null) {
            // no swap
        } else if (pn1 == null) {
            // replace n2
            pn2.replaceChild(n1, n2);
        } else if (pn2 == null) {
            // replace n1
            pn1.replaceChild(n2, n1);
        } else {
            // swap
            var ns = n1.nextSibling;
            pn2.replaceChild(n1, n2);
            pn1.insertBefore(n2, ns);
        }
    }, // swap
    
    
    getClassParameter: function(element, name) {
        if (typeof element.className == "string") {
            var match = name + "[";
            var classes = element.className.split(/\s+/);
            var i;
            for (i = 0; i < classes.length; i++) {
                var cl = classes[i];
                if (cl.length > match.length
                        && cl.charAt(cl.length - 1) == "]"
                        && cl.substring(0, match.length) == match) {
                    return unescape(cl.substring(0, cl.length - 1).substring(match.length));
                }
            }
        }
        return null;
    } // getClassParameter
    
    
}; // DOMUtil


Script.namespace("com.civicscience.geom");

com.civicscience.geom.Point = function(x, y) {
    this.x = 0 + x;
    this.y = 0 + y;
    this.distance = function(pt) {
        return Math.sqrt(Math.pow(this.x - pt.x, 2) + Math.pow(this.y - pt.y, 2));
    };
    this.clone = function() {
        return new com.civicscience.geom.Point(this.x, this.y);
    };
    this.toString = function() {
        return "(" + this.x + "," + this.y + ")";
    };
}; // com.civicscience.geom.Point

com.civicscience.geom.Dimensions = function(width, height) {
    this.width = width;
    this.height = height;
    this.clone = function() {
        return new com.civicscience.geom.Dimensions(this.width, this.height);
    };
    this.toString = function() {
        return this.width + "x" + this.height;
    };
}; // com.civicscience.geom.Dimensions

com.civicscience.geom.Rectangle = function(x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.location = function() {
        return new Point(this.x, this.y);
    };
    this.dimensions = function() {
        return new Dimensions(this.width, this.height);
    };
    this.area = function() {
        return Math.abs(this.width * this.height);
    };
    this.top = function() {
        return Math.min(this.y, this.y + this.height);
    };
    this.bottom = function() {
        return Math.max(this.y, this.y + this.height);
    };
    this.left = function() {
        return Math.min(this.x, this.x + this.width);
    };
    this.right = function() {
        return Math.max(this.x, this.x + this.width);
    };
    this.intersection = function(rect) {
        var t = Math.max(this.top(), rect.top());
        var b = Math.min(this.bottom(), rect.bottom());
        var l = Math.max(this.left(), rect.left());
        var r = Math.min(this.right(), rect.right());
        var w = Math.max(r - l, 0);
        var h = Math.max(b - t, 0);
        return new com.civicscience.geom.Rectangle(l, t, w, h);
    };
    this.contains = function(rect) {
        return rect.top() >= this.top()
                && rect.bottom() <= this.bottom()
                && rect.left() >= this.left()
                && rect.right() <= this.right();
    };
    this.clone = function() {
        return new com.civicscience.geom.Rectangle(this.x, this.y, this.width, this.height);
    };
    this.toString = function() {
        return "Rectangle[(" + this.x + "," + this.y + ");" + this.width + "x" + this.height + "]";
    };
}; // com.civicscience.geom.Rectangle

com.civicscience.geom.HTML = {
    
    // element bounds in document coordinates
    getElementBounds: function(elt) {
        var org = this.getElementOrigin(elt);
        var x = org.x + elt.offsetLeft;
        var y = org.y + elt.offsetTop;
        var w = elt.offsetWidth;
        var h = elt.offsetHeight;
        return new com.civicscience.geom.Rectangle(x, y, w, h);
    },
    
    // origin of element's local coordinate system, expressed in document coordinates
    getElementOrigin: function(elt) {
        var x = 0;
        var y = 0;
        if (elt.parentNode != null) {
            var p = elt.offsetParent;
            while (p != null) {
                x += p.offsetLeft;
                y += p.offsetTop;
                p = p.offsetParent;
            }
        }
        return new com.civicscience.geom.Point(x, y);
    },
    
    // element bounds not counting borders
    getBorderlessElementBounds: function(elt) {
        var bounds = this.getElementBounds(elt);
        var style = null;
        if (window.getComputedStyle) {
            style = window.getComputedStyle(elt, null);
        } else {
            style = elt.currentStyle;
        }
        if (style != null) {
            // assume pixel padding
            var t = parseInt(style.borderTopWidth) || 0;
            var r = parseInt(style.borderRightWidth) || 0;
            var b = parseInt(style.borderBottomWidth) || 0;
            var l = parseInt(style.borderLeftWidth) || 0;
            if (t || r || b || l) {
                // adjust bounds
                var x = bounds.x + l;
                var y = bounds.y + t;
                var w = bounds.width - l - r;
                var h = bounds.height - t - b;
                bounds = new com.civicscience.geom.Rectangle(x, y, w, h);
            }
        }
        return bounds;
    },
    
    // viewport bounds in document coordinates
    getViewportBounds: function() {
        var x = 0;
        var y = 0;
        var w = 0;
        var h = 0;
        if (window.pageXOffset !== undefined) {
            x = window.pageXOffset;
            y = window.pageYOffset;
        } else if (document.documentElement && document.documentElement.scrollLeft !== undefined) {
            x = document.documentElement.scrollLeft;
            y = document.documentElement.scrollTop;
        } else if (document.body.scrollLeft !== undefined) {
            x = document.body.scrollLeft;
            y = document.body.scrollTop;
        }
        if (window.innerWidth !== undefined) {
            w = window.innerWidth;
            h = window.innerHeight;
        } else if (document.documentElement && document.documentElement.clientWidth !== undefined) {
            w = document.documentElement.clientWidth;
            h = document.documentElement.clientHeight;
        } else if (document.body.clientWidth !== undefined) {
            w = document.body.clientWidth;
            h = document.body.clientHeight;
        }
        return new com.civicscience.geom.Rectangle(x, y, w, h);
    }
    
}; // com.civicscience.geom.HTML


var Geometry = {
        
        
    getLeft: function(element) {
        var left = 0;
        while (element) {
            left += element.offsetLeft;
            element = element.offsetParent;
        }
        return left;
    }, // getLeft
        
        
    getTop: function(element) {
        var top = 0;
        while (element) {
            top += element.offsetTop;
            element = element.offsetParent;
        }
        return top;
    }, // getTop


    getViewportWidth: function() {
        if (window.innerWidth !== undefined) {
            return window.innerWidth;
        } else if (document.documentElement && document.documentElement.clientWidth !== undefined) {
            return document.documentElement.clientWidth;
        } else if (document.body.clientWidth !== undefined) {
            return document.body.clientWidth;
        } else {
            return undefined;
        }
    }, // getViewportWidth


    getViewportHeight: function() {
        if (window.innerHeight !== undefined) {
            return window.innerHeight;
        } else if (document.documentElement && document.documentElement.clientHeight !== undefined) {
            return document.documentElement.clientHeight;
        } else if (document.body.clientHeight !== undefined) {
            return document.body.clientHeight;
        } else {
            return undefined;
        }
    }, // getViewportHeight
    
    
    getViewportXOffset: function() {
        if (window.pageXOffset !== undefined) {
            return window.pageXOffset;
        } else if (document.documentElement && document.documentElement.scrollLeft !== undefined) {
            return document.documentElement.scrollLeft;
        } else if (document.body.scrollLeft !== undefined) {
            return document.body.scrollLeft;
        } else {
            return undefined;
        }
    }, // getViewportXOffset
    
    
    getViewportYOffset: function() {
        if (window.pageYOffset !== undefined) {
            return window.pageYOffset;
        } else if (document.documentElement && document.documentElement.scrollTop !== undefined) {
            return document.documentElement.scrollTop;
        } else if (document.body.scrollTop !== undefined) {
            return document.body.scrollTop;
        } else {
            return undefined;
        }
    } // getViewportXOffset
    

}; // Geometry

// for testing interface implementation
Interface = function() {
    var methods = {};
    // make the supplied interface a subinterface of this one
    this.subinterface = function(iface) {
        var m;
        for (m in methods) {
            iface.method(m);
        }
        return iface;
    };
    // add a method name to the interface
    this.method = function(name) {
        methods[name] = true;
    };
    // test whether an object implements this interface
    this.assert = function(obj) {
        var m;
        for (m in methods) {
            if (typeof obj[m] != "function") {
                throw new Error("Object does not implement the \"" + m + "\" method.");
            }
        }
    };
}; // Interface
