LowPro = {};
LowPro.Version = '0.5';
LowPro.CompatibleWithPrototype = '1.6';

if (Prototype.Version.indexOf(LowPro.CompatibleWithPrototype) != 0 && window.console && window.console.warn)
  console.warn("This version of Low Pro is tested with Prototype " + LowPro.CompatibleWithPrototype + 
                  " it may not work as expected with this version (" + Prototype.Version + ")");

if (!Element.addMethods) 
  Element.addMethods = function(o) { Object.extend(Element.Methods, o) };

// Simple utility methods for working with the DOM
DOM = {};

// DOMBuilder for prototype
DOM.Builder = {
	tagFunc : function(tag) {
    return function() {
     var attrs, children;
     if (arguments.length>0) {
       if (arguments[0].constructor == Object) {
         attrs = arguments[0];
         children = Array.prototype.slice.call(arguments, 1);
       } else {
         children = arguments;
       };
       children = $A(children).flatten()
     }
     return DOM.Builder.create(tag, attrs, children);
    };
  },
	create : function(tag, attrs, children) {
		attrs = attrs || {}; children = children || []; tag = tag.toLowerCase();
		var el = new Element(tag, attrs);
	  
		for (var i=0; i<children.length; i++) {
			if (typeof children[i] == 'string') 
			  children[i] = document.createTextNode(children[i]);
			el.appendChild(children[i]);
		}
		return $(el);
	}
};

// Automatically create node builders as $tagName.
(function() { 
	var els = ("p|div|span|strong|em|img|table|tr|td|th|thead|tbody|tfoot|pre|code|" + 
				     "h1|h2|h3|h4|h5|h6|ul|ol|li|form|input|textarea|legend|fieldset|" + 
				     "select|option|blockquote|cite|br|hr|dd|dl|dt|address|a|button|abbr|acronym|" +
				     "script|link|style|bdo|ins|del|object|param|col|colgroup|optgroup|caption|" + 
				     "label|dfn|kbd|samp|var").split("|");
  var el, i=0;
	while (el = els[i++]) 
	  window['$' + el] = DOM.Builder.tagFunc(el);
})();

DOM.Builder.fromHTML = function(html) {
  var root;
  if (!(root = arguments.callee._root))
    root = arguments.callee._root = document.createElement('div');
  root.innerHTML = html;
  return root.childNodes[0];
};



// Wraps the 1.6 contentloaded event for backwards compatibility
//
// Usage:
//
// Event.onReady(callbackFunction);
Object.extend(Event, {
  onReady : function(f) {
    if (document.body) f();
    else document.observe('dom:loaded', f);
  }
});

// Based on event:Selectors by Justin Palmer
// http://encytemedia.com/event-selectors/
//
// Usage:
//
// Event.addBehavior({
//      "selector:event" : function(event) { /* event handler.  this refers to the element. */ },
//      "selector" : function() { /* runs function on dom ready.  this refers to the element. */ }
//      ...
// });
//
// Multiple calls will add to exisiting rules.  Event.addBehavior.reassignAfterAjax and
// Event.addBehavior.autoTrigger can be adjusted to needs.
Event.addBehavior = function(rules) {
  var ab = this.addBehavior;
  Object.extend(ab.rules, rules);
  
  if (!ab.responderApplied) {
    Ajax.Responders.register({
      onComplete : function() { 
        if (Event.addBehavior.reassignAfterAjax) 
          setTimeout(function() { ab.reload() }, 10);
      }
    });
    ab.responderApplied = true;
  }
  
  if (ab.autoTrigger) {
    this.onReady(ab.load.bind(ab, rules));
  }
  
};

Event.delegate = function(rules) {
  return function(e) {
      var element = $(e.element());
      for (var selector in rules)
        if (element.match(selector)) return rules[selector].apply(this, $A(arguments));
    }
}

Object.extend(Event.addBehavior, {
  rules : {}, cache : [],
  reassignAfterAjax : false,
  autoTrigger : true,
  
  load : function(rules) {
    for (var selector in rules) {
      var observer = rules[selector];
      var sels = selector.split(',');
      sels.each(function(sel) {
        var parts = sel.split(/:(?=[a-z]+$)/), css = parts[0], event = parts[1];
        $$(css).each(function(element) {
          if (event) {
            observer = Event.addBehavior._wrapObserver(observer);
            $(element).observe(event, observer);
            Event.addBehavior.cache.push([element, event, observer]);
          } else {
            if (!element.$$assigned || !element.$$assigned.include(observer)) {
              if (observer.attach) observer.attach(element);
              
              else observer.call($(element));
              element.$$assigned = element.$$assigned || [];
              element.$$assigned.push(observer);
            }
          }
        });
      });
    }
  },
  
  unload : function() {
    this.cache.each(function(c) {
      Event.stopObserving.apply(Event, c);
    });
    this.cache = [];
  },
  
  reload: function() {
    var ab = Event.addBehavior;
    ab.unload(); 
    ab.load(ab.rules);
  },
  
  _wrapObserver: function(observer) {
    return function(event) {
      if (observer.call(this, event) === false) event.stop(); 
    }
  }
  
});

Event.observe(window, 'unload', Event.addBehavior.unload.bind(Event.addBehavior));

// A silly Prototype style shortcut for the reckless
$$$ = Event.addBehavior.bind(Event);

// Behaviors can be bound to elements to provide an object orientated way of controlling elements
// and their behavior.  Use Behavior.create() to make a new behavior class then use attach() to
// glue it to an element.  Each element then gets it's own instance of the behavior and any
// methods called onxxx are bound to the relevent event.
// 
// Usage:
// 
// var MyBehavior = Behavior.create({
//   onmouseover : function() { this.element.addClassName('bong') } 
// });
//
// Event.addBehavior({ 'a.rollover' : MyBehavior });
// 
// If you need to pass additional values to initialize use:
//
// Event.addBehavior({ 'a.rollover' : MyBehavior(10, { thing : 15 }) })
//
// You can also use the attach() method.  If you specify extra arguments to attach they get passed to initialize.
//
// MyBehavior.attach(el, values, to, init);
//
// Finally, the rawest method is using the new constructor normally:
// var draggable = new Draggable(element, init, vals);
//
// Each behaviour has a collection of all its instances in Behavior.instances
//
var Behavior = {
  create: function() {
    var parent = null, properties = $A(arguments);
    if (Object.isFunction(properties[0]))
      parent = properties.shift();

      var behavior = function() { 
        var behavior = arguments.callee;
        if (!this.initialize) {
          var args = $A(arguments);

          return function() {
            var initArgs = [this].concat(args);
            behavior.attach.apply(behavior, initArgs);
          };
        } else {
          var args = (arguments.length == 2 && arguments[1] instanceof Array) ? 
                      arguments[1] : Array.prototype.slice.call(arguments, 1);

          this.element = $(arguments[0]);
          this.initialize.apply(this, args);
          behavior._bindEvents(this);
          behavior.instances.push(this);
        }
      };

    Object.extend(behavior, Class.Methods);
    Object.extend(behavior, Behavior.Methods);
    behavior.superclass = parent;
    behavior.subclasses = [];
    behavior.instances = [];

    if (parent) {
      var subclass = function() { };
      subclass.prototype = parent.prototype;
      behavior.prototype = new subclass;
      parent.subclasses.push(behavior);
    }

    for (var i = 0; i < properties.length; i++)
      behavior.addMethods(properties[i]);

    if (!behavior.prototype.initialize)
      behavior.prototype.initialize = Prototype.emptyFunction;

    behavior.prototype.constructor = behavior;

    return behavior;
  },
  Methods : {
    attach : function(element) {
      return new this(element, Array.prototype.slice.call(arguments, 1));
    },
    _bindEvents : function(bound) {
      for (var member in bound)
        if (member.match(/^on(.+)/) && typeof bound[member] == 'function')
          bound.element.observe(RegExp.$1, Event.addBehavior._wrapObserver(bound[member].bindAsEventListener(bound)));
    }
  }
};



Remote = Behavior.create({
  initialize: function(options) {
    if (this.element.nodeName == 'FORM') new Remote.Form(this.element, options);
    else new Remote.Link(this.element, options);
  }
});

Remote.Base = {
  initialize : function(options) {
    this.options = Object.extend({
      evaluateScripts : true
    }, options || {});
    
    this._bindCallbacks();
  },
  _makeRequest : function(options) {
    if (options.update) new Ajax.Updater(options.update, options.url, options);
    else new Ajax.Request(options.url, options);
    return false;
  },
  _bindCallbacks: function() {
    $w('onCreate onComplete onException onFailure onInteractive onLoading onLoaded onSuccess').each(function(cb) {
      if (Object.isFunction(this.options[cb]))
        this.options[cb] = this.options[cb].bind(this);
    }.bind(this));
  }
}

Remote.Link = Behavior.create(Remote.Base, {
  onclick : function() {
    var options = Object.extend({ url : this.element.href, method : 'get' }, this.options);
    return this._makeRequest(options);
  }
});


Remote.Form = Behavior.create(Remote.Base, {
  onclick : function(e) {
    var sourceElement = e.element();
    
    if (['input', 'button'].include(sourceElement.nodeName.toLowerCase()) && 
        sourceElement.type == 'submit')
      this._submitButton = sourceElement;
  },
  onsubmit : function() {
    var options = Object.extend({
      url : this.element.action,
      method : this.element.method || 'get',
      parameters : this.element.serialize({ submit: this._submitButton.name })
    }, this.options);
    this._submitButton = null;
    return this._makeRequest(options);
  }
});

Observed = Behavior.create({
  initialize : function(callback, options) {
    this.callback = callback.bind(this);
    this.options = options || {};
    this.observer = (this.element.nodeName == 'FORM') ? this._observeForm() : this._observeField();
  },
  stop: function() {
    this.observer.stop();
  },
  _observeForm: function() {
    return (this.options.frequency) ? new Form.Observer(this.element, this.options.frequency, this.callback) :
                                      new Form.EventObserver(this.element, this.callback);
  },
  _observeField: function() {
    return (this.options.frequency) ? new Form.Element.Observer(this.element, this.options.frequency, this.callback) :
                                      new Form.Element.EventObserver(this.element, this.callback);
  }
});



var Application = {
  authenticityToken: function() {
    return $('authenticity-token').content;
  }
}

var Anchor = Anchor || new Object();
Anchor.Methods = {
  // Convert the anchor link into a form submission
  submit: function(anchor) {
    var options = Object.extend({
      'method': 'post',
      'parameters': {}
    }, arguments[1] || {});
    if (Object.isString(options.parameters))
      options.parameters = options.parameters.toQueryParams();
    else if (Object.isHash(options.parameters))
      options.parameters = options.parameters.toObject();

    var params = Object.clone(options.parameters);
    params['authenticity_token'] = Application.authenticityToken();
    
    options.method = options.method.toLowerCase();
    
    if (!['get', 'post'].include(options.method)) {
      // simulate other verbs over post
      params['_method'] = options.method;
      options.method = 'post';
    }
    
    var form = new Element('form', {method: options.method, action: anchor.href});
    form.setStyle({display: 'none'});
    anchor.insert({after: form});
    $H(params).each(function(param) {
      var name = param[0], value = param[1];
      form.insert(new Element('input', {type: 'hidden', name: name, value: value}));
    });
    form.submit();
  }
}
Element.addMethods('a', Anchor.Methods);

var DefaultInputValue = Behavior.create({
  initialize: function() {
    this.onblur();
  },
  
  onfocus: function() {
    if(this.element.value == this.element.title) {
      this.element.removeClassName('inactive');
      this.element.value = '';
    }
  },
  
  onblur: function() {
    if(this.element.value.empty()) {
      this.element.addClassName('inactive');
      this.element.value = this.element.title;
    }
  }
});

Event.addBehavior({
  'form#new_location:submit': function(event) {
    event.stop();
    this.request({
      onLoading:  function() { this.addClassName('loading'); }.bind(this),
      onComplete: function() { this.removeClassName('loading'); }.bind(this),
      onSuccess:  function() { this.reset(); }.bind(this),
      onFailure: Map.ajaxError
    });
  },
  
  '#location_street': DefaultInputValue,
  
  'a.destroy:click': function(event) {
    event.stop();
    if (confirm('Do you really want to delete this?')) {
      this.submit({method: 'delete'});
    }
  },
  
  'a.restore:click': function(event) {
    event.stop();
    if (confirm('Do you really want to restore this?')) {
      this.submit({method: 'put'});
    }
  },
  
  'a.show:click': function(event) {
    event.stop();
    Map.findLocation(this.href);
  },

  'a.show_cities:click': function(event) {
    event.stop();
    Map.getCities(null, 'Map.mapCitiesAndZoom');
  },

	'a.show_states:click': function(event) {
		event.stop();
		var ne = "51.234407,-66.972656";
		var sw = "23.966176,-128.496094"
		
		var bounds = new GLatLngBounds(new GLatLng(23.966176,-128.496094), new GLatLng(51.234407,-66.972656));
		Map.getCities(bounds, 'Map.mapCitiesAndZoom')
	}
});

Event.addBehavior.reassignAfterAjax = true;


// Date methods from 
// http://delete.me.uk/2005/03/iso8601.html
// http://mikewest.org/projects/files/PerfectTime/PerfectTime.js
//

// Parse ISO 8601 type times (e.g. hCalendar)
//     based on Paul Sowden's method, tweaked to match up 
//     with 'real world' hCalendar usage:
//
//         http://delete.me.uk/2005/03/iso8601.html
//
Object.extend(Date, {
  ISO8601Regex: /(\d{4})(?:-?(\d{2})(?:-?(\d{2})(?:T(\d{2}):?(\d{2})(?::?(\d{2})(?:[.]?(\d+))?)?(?:Z|(?:([+-])(\d{2}):?(\d{2}))?)?)?)?)?/,
  
  parseISO8601: function(string, adjustOffset) {
    if (adjustOffset == undefined) adjustOffset = true;
    
    var match = string.match(Date.ISO8601Regex);
    
    var date = new Date(
      match[1],             // year
      (match[2] || 1) - 1,  // month - 1:  Because JS months are 0-11
      match[3] || 1,        // day
      match[4] || null,     // hour
      match[5] || null,     // minute
      match[6] || null,     // second
      // Must be between 0 and 999
      match[7] ? Number("0." + match[7]) * 1000 : null // millisecond
    );

    if (adjustOffset) {
      var offset = 0;
      if (match[8]) {
        offset = (Number(match[9]) * 60 + Number(match[10]));
        if (match[8] == "+") { offset *= -1; }
      }

      date.adjustTimezoneOffset(offset - date.getTimezoneOffset());
    }
    
    return date;
  }
});

Object.extend(Date.prototype, {
  minutesSinceMidnight: function() {
    return this.getHours() * 60 + this.getMinutes();
  },
  
  adjustTimezoneOffset: function(offset) {
    this.setTime(Number(this) + (offset * 60 * 1000));
    return this
  },
  
  adjustCurrentTimezoneOffset: function() {
    return this.adjustTimezoneOffset(-this.getTimezoneOffset());
  },
  
  /* Returns a new Time where one or more of the elements have been changed according
   * to the +options+ parameter. The time options (hour, minute, sec, usec) reset
   * cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0.
   * If the hour and minute is passed, then sec and usec is set to 0.
   */
  change: function(options) {
    return new Date(
      options.year  || this.getFullYear(),
      (options.month ? options.month - 1 : this.getMonth()),
      options.day   || this.getDay(),
      options.hour  || this.getHours(),
      options.min   || (options.hour ? 0 : this.getMinutes()),
      options.sec   || ((options.hour || options.min) ? 0 : this.getSeconds()),
      options.usec  || ((options.hour || options.min || options.sec) ? 0 : this.getMilliseconds())
    )
  }
});

// From http://github.com/Caged/javascript-bits/tree/master/datetime/format.js
Object.extend(Date.prototype, {
  /**
   * @param format {String} The string used to format the date.
   * Example:  new Date().strftime("%A %I:%M %p")
   */
  strftime: function(format) {
    var day = this.getUTCDay(), month = this.getUTCMonth();
    var hours = this.getUTCHours(), minutes = this.getUTCMinutes();
    function pad(num) { return num.toPaddedString(2); };
 
    return format.gsub(/\%([aAbBcdDHiImMpSwyY])/, function(part) {
      switch(part[1]) {
        case 'a': return $w("Sun Mon Tue Wed Thu Fri Sat")[day]; break;
        case 'A': return $w("Sunday Monday Tuesday Wednesday Thursday Friday Saturday")[day]; break;
        case 'b': return $w("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec")[month]; break;
        case 'B': return $w("January February March April May June July August September October November December")[month]; break;
        case 'c': return this.toString(); break;
        case 'd': return this.getUTCDate(); break;
        case 'D': return pad(this.getUTCDate()); break;
        case 'H': return pad(hours); break;
        case 'i': return (hours === 12 || hours === 0) ? 12 : (hours + 12) % 12; break;
        case 'I': return pad((hours === 12 || hours === 0) ? 12 : (hours + 12) % 12); break;
        case 'm': return pad(month + 1); break;
        case 'M': return pad(minutes); break;
        case 'p': return hours > 11 ? 'PM' : 'AM'; break;
        case 'S': return pad(this.getUTCSeconds()); break;
        case 'w': return day; break;
        case 'y': return pad(this.getUTCFullYear() % 100); break;
        case 'Y': return this.getUTCFullYear().toString(); break;
      }
    }.bind(this));
  }
});

// Clusterer.js - marker clustering routines for Google Maps apps
//
// Using these routines is very easy.
//
// 1) Load the routines into your code:
//
//        <script src="http://www.acme.com/javascript/Clusterer.js" type="text/javascript"></script>
//
// 2) Create a Clusterer object, passing it your map object:
//
//        var clusterer = new Clusterer( map );
//
// 3) Wherever you now do map.addOverlay( marker ), instead call
//    clusterer.AddMarker( marker, title ).  The title is just a
//    short descriptive string to use in the cluster info-boxes.
//
// 4) If you are doing any map.removeOverlay( marker ) calls, change those
//    to clusterer.RemoveMarker( marker ).
//
// That's it!  Everything else happens automatically.
//
//
// The current version of this code is always available at:
// http://www.acme.com/javascript/
//
//
// Copyright © 2005,2006 by Jef Poskanzer <jef@mail.acme.com>.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// For commentary on this license please see http://www.acme.com/license.html


// Constructor.
Clusterer = function ( map )
    {
    this.map = map;
    this.markers = [];
    this.clusters = [];
    this.timeout = null;
    this.currentZoomLevel = map.getZoom();

    this.maxVisibleMarkers = Clusterer.defaultMaxVisibleMarkers;
    this.gridSize = Clusterer.defaultGridSize;
    this.minMarkersPerCluster = Clusterer.defaultMinMarkersPerCluster;
    this.maxLinesPerInfoBox = Clusterer.defaultMaxLinesPerInfoBox;
    this.icon = Clusterer.defaultIcon;

    GEvent.addListener( map, 'zoomend', Clusterer.MakeCaller( Clusterer.Display, this ) );
    GEvent.addListener( map, 'moveend', Clusterer.MakeCaller( Clusterer.Display, this ) );
    GEvent.addListener( map, 'infowindowclose', Clusterer.MakeCaller( Clusterer.PopDown, this ) );
    };


Clusterer.defaultMaxVisibleMarkers = 150;
Clusterer.defaultGridSize = 5;
Clusterer.defaultMinMarkersPerCluster = 5;
Clusterer.defaultMaxLinesPerInfoBox = 10;

Clusterer.defaultIcon = new GIcon();
Clusterer.defaultIcon.image = 'http://www.acme.com/resources/images/markers/blue_large.PNG';
Clusterer.defaultIcon.shadow = 'http://www.acme.com/resources/images/markers/shadow_large.PNG';
Clusterer.defaultIcon.iconSize = new GSize( 30, 51 );
Clusterer.defaultIcon.shadowSize = new GSize( 56, 51 );
Clusterer.defaultIcon.iconAnchor = new GPoint( 13, 34 );
Clusterer.defaultIcon.infoWindowAnchor = new GPoint( 13, 3 );
Clusterer.defaultIcon.infoShadowAnchor = new GPoint( 27, 37 );


// Call this to change the cluster icon.
Clusterer.prototype.SetIcon = function ( icon )
    {
    this.icon = icon;
    };


// Changes the maximum number of visible markers before clustering kicks in.
Clusterer.prototype.SetMaxVisibleMarkers = function ( n )
    {
    this.maxVisibleMarkers = n;
    };


// Sets the minumum number of markers for a cluster.
Clusterer.prototype.SetMinMarkersPerCluster = function ( n )
    {
    this.minMarkersPerCluster = n;
    };


// Sets the maximum number of lines in an info box.
Clusterer.prototype.SetMaxLinesPerInfoBox = function ( n )
    {
    this.maxLinesPerInfoBox = n;
    };


// Call this to add a marker.
Clusterer.prototype.AddMarker = function ( marker, title )
    {
    if ( marker.setMap != null )
	marker.setMap( this.map );

    marker.title = title;
    marker.onMap = false;
    this.markers.push( marker );
    this.DisplayLater();
    };


// Call this to remove a marker.
Clusterer.prototype.RemoveMarker = function ( marker )
    {
    for ( var i = 0; i < this.markers.length; ++i )
	if ( this.markers[i] == marker )
	    {
	    if ( marker.onMap )
		this.map.removeOverlay( marker );
	    for ( var j = 0; j < this.clusters.length; ++j )
		{
		var cluster = this.clusters[j];
		if ( cluster != null )
		    {
		    for ( var k = 0; k < cluster.markers.length; ++k )
			if ( cluster.markers[k] == marker )
			    {
			    cluster.markers[k] = null;
			    --cluster.markerCount;
			    break;
			    }
		    if ( cluster.markerCount == 0 )
			{
			this.ClearCluster( cluster );
			this.clusters[j] = null;
			}
		    else if ( cluster == this.poppedUpCluster )
			Clusterer.RePop( this );
		    }
		}
	    this.markers[i] = null;
	    break;
	    }
    this.DisplayLater();
    };


Clusterer.prototype.DisplayLater = function ()
    {
    if ( this.timeout != null )
	clearTimeout( this.timeout );
    this.timeout = setTimeout( Clusterer.MakeCaller( Clusterer.Display, this ), 50 );
    };


Clusterer.Display = function ( clusterer )
    {
    var i, j, marker, cluster;

    clearTimeout( clusterer.timeout );

    var newZoomLevel = clusterer.map.getZoom();
    if ( newZoomLevel != clusterer.currentZoomLevel )
	{
	// When the zoom level changes, we have to remove all the clusters.
	for ( i = 0; i < clusterer.clusters.length; ++i )
	    if ( clusterer.clusters[i] != null )
		{
		clusterer.ClearCluster( clusterer.clusters[i] );
		clusterer.clusters[i] = null;
		}
	clusterer.clusters.length = 0;
	clusterer.currentZoomLevel = newZoomLevel;
	}

    // Get the current bounds of the visible area.
    var bounds = clusterer.map.getBounds();

    // Expand the bounds a little, so things look smoother when scrolling
    // by small amounts.
    var sw = bounds.getSouthWest();
    var ne = bounds.getNorthEast();
    var dx = ne.lng() - sw.lng();
    var dy = ne.lat() - sw.lat();
    if ( dx < 300 && dy < 150 )
	{
	dx *= 0.10;
	dy *= 0.10;
	bounds = new GLatLngBounds(
	  new GLatLng( sw.lat() - dy, sw.lng() - dx ),
	  new GLatLng( ne.lat() + dy, ne.lng() + dx ) );
	}

    // Partition the markers into visible and non-visible lists.
    var visibleMarkers = [];
    var nonvisibleMarkers = [];
    for ( i = 0; i < clusterer.markers.length; ++i )
	{
	marker = clusterer.markers[i];
	if ( marker != null )
	    if ( bounds.contains( marker.getPoint() ) )
		visibleMarkers.push( marker );
	    else
		nonvisibleMarkers.push( marker );
	}

    // Take down the non-visible markers.
    for ( i = 0; i < nonvisibleMarkers.length; ++i )
	{
	marker = nonvisibleMarkers[i];
	if ( marker.onMap )
	    {
	    clusterer.map.removeOverlay( marker );
	    marker.onMap = false;
	    }
	}

    // Take down the non-visible clusters.
    for ( i = 0; i < clusterer.clusters.length; ++i )
	{
	cluster = clusterer.clusters[i];
	if ( cluster != null && ! bounds.contains( cluster.marker.getPoint() ) && cluster.onMap )
	    {
	    clusterer.map.removeOverlay( cluster.marker );
	    cluster.onMap = false;
	    }
	}

    // Clustering!  This is some complicated stuff.  We have three goals
    // here.  One, limit the number of markers & clusters displayed, so the
    // maps code doesn't slow to a crawl.  Two, when possible keep existing
    // clusters instead of replacing them with new ones, so that the app pans
    // better.  And three, of course, be CPU and memory efficient.
    if ( visibleMarkers.length > clusterer.maxVisibleMarkers )
	{
	// Add to the list of clusters by splitting up the current bounds
	// into a grid.
	var latRange = bounds.getNorthEast().lat() - bounds.getSouthWest().lat();
	var latInc = latRange / clusterer.gridSize;
	var lngInc = latInc / Math.cos( ( bounds.getNorthEast().lat() + bounds.getSouthWest().lat() ) / 2.0 * Math.PI / 180.0 );
	for ( var lat = bounds.getSouthWest().lat(); lat <= bounds.getNorthEast().lat(); lat += latInc )
	    for ( var lng = bounds.getSouthWest().lng(); lng <= bounds.getNorthEast().lng(); lng += lngInc )
		{
		cluster = new Object();
		cluster.clusterer = clusterer;
		cluster.bounds = new GLatLngBounds( new GLatLng( lat, lng ), new GLatLng( lat + latInc, lng + lngInc ) );
		cluster.markers = [];
		cluster.markerCount = 0;
		cluster.onMap = false;
		cluster.marker = null;
		clusterer.clusters.push( cluster );
		}

	// Put all the unclustered visible markers into a cluster - the first
	// one it fits in, which favors pre-existing clusters.
	for ( i = 0; i < visibleMarkers.length; ++i )
	    {
	    marker = visibleMarkers[i];
	    if ( marker != null && ! marker.inCluster )
		{
		for ( j = 0; j < clusterer.clusters.length; ++j )
		    {
		    cluster = clusterer.clusters[j];
		    if ( cluster != null && cluster.bounds.contains( marker.getPoint() ) )
			{
			cluster.markers.push( marker );
			++cluster.markerCount;
			marker.inCluster = true;
			}
		    }
		}
	    }

	// Get rid of any clusters containing only a few markers.
	for ( i = 0; i < clusterer.clusters.length; ++i )
	    if ( clusterer.clusters[i] != null && clusterer.clusters[i].markerCount < clusterer.minMarkersPerCluster )
		{
		clusterer.ClearCluster( clusterer.clusters[i] );
		clusterer.clusters[i] = null;
		}

	// Shrink the clusters list.
	for ( i = clusterer.clusters.length - 1; i >= 0; --i )
	    if ( clusterer.clusters[i] != null )
		break;
	    else
		--clusterer.clusters.length;

	// Ok, we have our clusters.  Go through the markers in each
	// cluster and remove them from the map if they are currently up.
	for ( i = 0; i < clusterer.clusters.length; ++i )
	    {
	    cluster = clusterer.clusters[i];
	    if ( cluster != null )
		{
		for ( j = 0; j < cluster.markers.length; ++j )
		    {
		    marker = cluster.markers[j];
		    if ( marker != null && marker.onMap )
			{
			clusterer.map.removeOverlay( marker );
			marker.onMap = false;
			}
		    }
		}
	    }

	// Now make cluster-markers for any clusters that need one.
	for ( i = 0; i < clusterer.clusters.length; ++i )
	    {
	    cluster = clusterer.clusters[i];
	    if ( cluster != null && cluster.marker == null )
		{
		// Figure out the average coordinates of the markers in this
		// cluster.
		var xTotal = 0.0, yTotal = 0.0;
		for ( j = 0; j < cluster.markers.length; ++j )
		    {
		    marker = cluster.markers[j];
		    if ( marker != null )
			{
			xTotal += ( + marker.getPoint().lng() );
			yTotal += ( + marker.getPoint().lat() );
			}
		    }
		var location = new GLatLng( yTotal / cluster.markerCount, xTotal / cluster.markerCount );
		marker = new GMarker( location, { icon: clusterer.icon } );
		cluster.marker = marker;
		GEvent.addListener( marker, 'click', Clusterer.MakeCaller( Clusterer.PopUp, cluster ) );
		}
	    }
	}

    // Display the visible markers not already up and not in clusters.
    for ( i = 0; i < visibleMarkers.length; ++i )
	{
	marker = visibleMarkers[i];
	if ( marker != null && ! marker.onMap && ! marker.inCluster )
	    {
	    clusterer.map.addOverlay( marker );
	    if ( marker.addedToMap != null )
		marker.addedToMap();
	    marker.onMap = true;
	    }
	}

    // Display the visible clusters not already up.
    for ( i = 0; i < clusterer.clusters.length; ++i )
	{
	cluster = clusterer.clusters[i];
	if ( cluster != null && ! cluster.onMap && bounds.contains( cluster.marker.getPoint() ) )
	    {
	    clusterer.map.addOverlay( cluster.marker );
	    cluster.onMap = true;
	    }
	}

    // In case a cluster is currently popped-up, re-pop to get any new
    // markers into the infobox.
    Clusterer.RePop( clusterer );
    };


Clusterer.PopUp = function ( cluster )
    {
    var clusterer = cluster.clusterer;
    var html = '<table width="300">';
    var n = 0;
    for ( var i = 0; i < cluster.markers.length; ++i )
	{
	var marker = cluster.markers[i];
	if ( marker != null )
	    {
	    ++n;
	    html += '<tr><td>';
	    if ( marker.getIcon().smallImage != null )
		html += '<img src="' + marker.getIcon().smallImage + '">';
	    else
		html += '<img src="' + marker.getIcon().image + '" width="' + ( marker.getIcon().iconSize.width / 2 ) + '" height="' + ( marker.getIcon().iconSize.height / 2 ) + '">';
	    html += '</td><td>' + marker.title + '</td></tr>';
	    if ( n == clusterer.maxLinesPerInfoBox - 1 && cluster.markerCount > clusterer.maxLinesPerInfoBox  )
		{
		html += '<tr><td colspan="2">...and ' + ( cluster.markerCount - n ) + ' more</td></tr>';
		break;
		}
	    }
	}
    html += '</table>';
    clusterer.map.closeInfoWindow();
    cluster.marker.openInfoWindowHtml( html );
    clusterer.poppedUpCluster = cluster;
    };


Clusterer.RePop = function ( clusterer )
    {
    if ( clusterer.poppedUpCluster != null )
	Clusterer.PopUp( clusterer.poppedUpCluster );
    };


Clusterer.PopDown = function ( clusterer )
    {
    clusterer.poppedUpCluster = null;
    };


Clusterer.prototype.ClearCluster = function ( cluster )
    {
    var i, marker;

    for ( i = 0; i < cluster.markers.length; ++i )
	if ( cluster.markers[i] != null )
	    {
	    cluster.markers[i].inCluster = false;
	    cluster.markers[i] = null;
	    }
    cluster.markers.length = 0;
    cluster.markerCount = 0;
    if ( cluster == this.poppedUpCluster )
	this.map.closeInfoWindow();
    if ( cluster.onMap )
	{
	this.map.removeOverlay( cluster.marker );
	cluster.onMap = false;
	}
    };


// This returns a function closure that calls the given routine with the
// specified arg.
Clusterer.MakeCaller = function ( func, arg )
    {
    return function () { func( arg ); };
    };


// Augment GMarker so it handles markers that have been created but
// not yet addOverlayed.

GMarker.prototype.setMap = function ( map )
    {
    this.map = map;
    };

GMarker.prototype.addedToMap = function ()
    {
    this.map = null;
    };

GMarker.prototype.origOpenInfoWindow = GMarker.prototype.openInfoWindow;
GMarker.prototype.openInfoWindow = function ( node, opts )
    {
    if ( this.map != null )
	return this.map.openInfoWindow( this.getPoint(), node, opts );
    else
	return this.origOpenInfoWindow( node, opts );
    };

GMarker.prototype.origOpenInfoWindowHtml = GMarker.prototype.openInfoWindowHtml;
GMarker.prototype.openInfoWindowHtml = function ( html, opts )
    {
    if ( this.map != null )
	return this.map.openInfoWindowHtml( this.getPoint(), html, opts );
    else
	return this.origOpenInfoWindowHtml( html, opts );
    };

GMarker.prototype.origOpenInfoWindowTabs = GMarker.prototype.openInfoWindowTabs;
GMarker.prototype.openInfoWindowTabs = function ( tabNodes, opts )
    {
    if ( this.map != null )
	return this.map.openInfoWindowTabs( this.getPoint(), tabNodes, opts );
    else
	return this.origOpenInfoWindowTabs( tabNodes, opts );
    };

GMarker.prototype.origOpenInfoWindowTabsHtml = GMarker.prototype.openInfoWindowTabsHtml;
GMarker.prototype.openInfoWindowTabsHtml = function ( tabHtmls, opts )
    {
    if ( this.map != null )
	return this.map.openInfoWindowTabsHtml( this.getPoint(), tabHtmls, opts );
    else
	return this.origOpenInfoWindowTabsHtml( tabHtmls, opts );
    };

GMarker.prototype.origShowMapBlowup = GMarker.prototype.showMapBlowup;
GMarker.prototype.showMapBlowup = function ( opts )
    {
    if ( this.map != null )
	return this.map.showMapBlowup( this.getPoint(), opts );
    else
	return this.origShowMapBlowup( opts );
    };


var Map = {
  markers: {},
  
  show: function() {
    var mapDiv = $('map')
    if (mapDiv && GBrowserIsCompatible()) {
      Map.map = new GMap2(document.getElementById("map"));
      Map.map.setCenter(new GLatLng(42.77309, -86.101754), 12);
      Map.map.enableScrollWheelZoom();
      Map.map.addControl(new GLargeMapControl());
      Map.map.addControl(new GMapTypeControl());
      Map.map.addControl(new GScaleControl());
      Map.map.addControl(new GOverviewMapControl());
    
      // listen for clicks
      GEvent.addListener(Map.map, 'click', function(overlay, point) {
        if (overlay) {
          // we now need a check here in case the overlay is the info window
          // only our icons will have a .html property
          if (overlay.html) {
            overlay.openInfoWindowHtml(overlay.html);
          }
        } else if (point) {
          //whatever you want to happen if you don't click on an overlay.
        }
      });
      
      GEvent.addListener(Map.map, "moveend", Map.refreshMarkers);
      GEvent.addListener(Map.map, "zoomend", Map.refreshMarkers);
    
      Map.clusterer = new Clusterer(Map.map);
      var clusterIcon = new GIcon();
      clusterIcon.image = '/images/cluster.png';
      clusterIcon.shadow = '/images/cluster_shadow.png';
      clusterIcon.iconSize = new GSize(60, 45);
      clusterIcon.shadowSize = new GSize(60, 45);
      clusterIcon.iconAnchor = new GPoint(30, 39);
      clusterIcon.infoWindowAnchor = new GPoint(30, 39);
      clusterIcon.infoShadowAnchor = new GPoint(0, 0);
      Map.clusterer.SetIcon(clusterIcon);
      Map.clusterer.SetMinMarkersPerCluster(25)
      Map.refreshMarkers();
      Event.observe(window, "unload", GUnload);
      mapDiv.removeClassName('loading');
    }
  },
  
  refreshMarkers: function() {
    var bounds = Map.map.getBounds();
    
    new Ajax.Request('/locations.json', {method: 'get', 
      parameters: {callback: 'Map.callback', 
        northeast: bounds.getNorthEast().toUrlValue(),
        southwest: bounds.getSouthWest().toUrlValue()},
      onFailure: Map.ajaxError} );
  },
	 
  callback: function(data) {
    if(!Map.errors(data)) {
      if(data.locations) data.locations.each(Map.mapLocation);
      if(data.location)     Map.mapLocationAndFocus(data);
    }
  },
  
  mapLocation: function(data) {
    if (!Map.markers[data.location.id]) {
      var point = new GLatLng(data.location.geocoding.geocode.latitude, data.location.geocoding.geocode.longitude);
      Map.markers[data.location.id] = new GMarker(point, {icon:Map.icon(data.location.signs)});
      Map.markers[data.location.id].html = '<span class="'+data.location.signs.toLowerCase()+'">'+data.location.street+'</span>'+data.location.city+', '+data.location.state+' '+data.location.zip+'<br>Reported '+Date.parseISO8601(data.location.created_at).strftime('%B %d, %i:%M %p')+'<br><a href="/locations/'+data.location.id+'/edit">Edit</a> | <a href="/locations/'+data.location.id+'" class="destroy">Remove</a>';
      Map.clusterer.AddMarker(Map.markers[data.location.id], data.location.street+', '+data.location.city+', '+data.location.state)
      return point;
    }
  },

  mapLocationAndFocus: function(data) {
    Map.map.setCenter(Map.mapLocation(data), 14);
    $('map').scrollTo();
    Map.showOverlay.delay(0.5, data.location);
  },

  showOverlay: function(location) {
    Map.markers[location.id].openInfoWindowHtml(Map.markers[location.id].html);
  },
  
  findLocation: function(location_path) {
    var id = location_path.match(/\d+$/)[0];
    if (Map.markers[id]) {
      Map.showOverlay({id: id});
      $('map').scrollTo();      
    } else {
      new Ajax.Request(location_path+'.json', {method: 'get', 
        parameters: {callback: 'Map.callback'},
        onFailure: Map.ajaxError} );
    }
  },
  
  errors: function(data) {
    $$('form .error').invoke('remove');
    if (data.errors) {
      var form = $$('form').first();
      var messages = data.errors.map(function(error) {
        var message = '';
        if (error[0] != 'base') message = error[0].capitalize() + ' ';
        return '<li>' + message + error[1] + '</li>';
      });
      form.insert({top: '<ul class="error">' + messages.join('') + '</ul>'});
    }
    return data.errors && !data.errors.empty();
  },
  
  ajaxError: function() {
    Map.errors({errors: [['base', "Sorry, there has been an unexpected error. We have been notified and will look into it. Please contact us if you'd like to know what we discover."]]});
  },
  
  icons: {},
  
  icon: function(color) {
    if (!Map.icons[color]) {
      Map.icons[color] = new GIcon(G_DEFAULT_ICON);
      Map.icons[color].image = '/images/'+ color.toLowerCase() + '.png';
      Map.icons[color].shadow = '/images/sign_shadow.png';
      Map.icons[color].iconSize = new GSize(13, 19);
      Map.icons[color].shadowSize = new GSize(13, 19);
      Map.icons[color].iconAnchor = new GPoint(7, 16);
      Map.icons[color].infoWindowAnchor = new GPoint(7, 16);    
    }
    return Map.icons[color];
  }
}

// Dunno why IE doesn't like dom:loaded.
if (Prototype.Browser.IE) {
  Event.observe(window, "load", Map.show);
} else {
  Event.observe(window, "dom:loaded", Map.show);
}


Object.extend(String.prototype, {
  // if a string doesn't end with str it appends it
  ensureEndsWith: function(str) {
    return this.endsWith(str) ? this : this + str;
  },
  
  // makes sure that string ends with px (for setting widths and heights)
  px: function() {
    return this.ensureEndsWith('px');
  }
});

Object.extend(Number.prototype, {
  // makes sure that number ends with px (for setting widths and heights)
  px: function() {
    return this.toString().px();
  }
});

var Window = {
  // returns correct dimensions for window, had issues with prototype's sometimes. this was ganked from apple.
  size: function() {
		var width  = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth);
		var height = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight);
		var x      = window.pageXOffset || (window.document.documentElement.scrollLeft || window.document.body.scrollLeft);
		var y      = window.pageYOffset || (window.document.documentElement.scrollTop || window.document.body.scrollTop);
		return {'width':width, 'height':height, 'x':x, 'y':y}
	}
}

var FancyZoomBox = {
  directory : 'images',
  zooming   : false,
  setup     : false,
  
  init: function(directory) {
    if (FancyZoomBox.setup) return;
    FancyZoomBox.setup = true;
    
    var ie = navigator.userAgent.match(/MSIE\s(\d)+/);
    if (ie) {
      var version = parseInt(ie[1]);
      Prototype.Browser['IE' + version.toString()] = true;
      Prototype.Browser.ltIE7 = (version < 7) ? true : false;
    }
    
    var html = '<div id="zoom" style="display:none;"> \
                  <table id="zoom_table" style="border-collapse:collapse; width:100%; height:100%;"> \
                    <tbody> \
                      <tr> \
                        <td class="tl" style="background:url(' + FancyZoomBox.directory + '/tl.png) 0 0 no-repeat; width:20px height:20px; overflow:hidden;" /> \
                        <td class="tm" style="background:url(' + FancyZoomBox.directory + '/tm.png) 0 0 repeat-x; height:20px; overflow:hidden;" /> \
                        <td class="tr" style="background:url(' + FancyZoomBox.directory + '/tr.png) 100% 0 no-repeat; width:20px height:20px; overflow:hidden;" /> \
                      </tr> \
                      <tr> \
                        <td class="ml" style="background:url(' + FancyZoomBox.directory + '/ml.png) 0 0 repeat-y; width:20px; overflow:hidden;" /> \
                        <td class="mm" style="background:#fff; vertical-align:top; padding:10px;"> \
                          <div id="zoom_content"> \
                          </div> \
                        </td> \
                        <td class="mr" style="background:url(' + FancyZoomBox.directory + '/mr.png) 100% 0 repeat-y;  width:20px; overflow:hidden;" /> \
                      </tr> \
                      <tr> \
                        <td class="bl" style="background:url(' + FancyZoomBox.directory + '/bl.png) 0 100% no-repeat; width:20px height:20px; overflow:hidden;" /> \
                        <td class="bm" style="background:url(' + FancyZoomBox.directory + '/bm.png) 0 100% repeat-x; height:20px; overflow:hidden;" /> \
                        <td class="br" style="background:url(' + FancyZoomBox.directory + '/br.png) 100% 100% no-repeat; width:20px height:20px; overflow:hidden;" /> \
                      </tr> \
                    </tbody> \
                  </table> \
                  <a href="#" title="Close" id="zoom_close" style="position:absolute; top:0; left:0;"> \
                    <img src="' + FancyZoomBox.directory + '/closebox.png" alt="Close" style="border:none; margin:0; padding:0;" /> \
                  </a> \
                </div>';
    
    var body  = $$('body').first();
    body.insert(html);
    
    FancyZoomBox.zoom = $('zoom');
    FancyZoomBox.zoom_table = $('zoom_table');
    FancyZoomBox.zoom_close = $('zoom_close');
    FancyZoomBox.zoom_content = $('zoom_content');
    FancyZoomBox.zoom_close.observe('click', FancyZoomBox.hide);
    FancyZoomBox.middle_row = $A([$$('td.ml'), $$('td.mm'), $$('td.mr')]).flatten();
    FancyZoomBox.cells = FancyZoomBox.zoom_table.select('td');
    
    // hide zoom if click fired is not inside zoom
    $$('html').first().observe('click', function(e) {
      var click_in_zoom = e.findElement('#zoom'),
          zoom_display  = FancyZoomBox.zoom.getStyle('display');
      if (zoom_display == 'block' && !click_in_zoom) {
        FancyZoomBox.hide(e);
      }
    });

    // esc to close zoom box
    $(document).observe('keyup', function(e) {
      var zoom_display = FancyZoomBox.zoom.getStyle('display');
      if (e.keyCode == Event.KEY_ESC && zoom_display == 'block') {
        FancyZoomBox.hide(e);
      }
    });
    
    // just use gifs as ie6 and below suck
    if (Prototype.Browser.ltIE7) {
      FancyZoomBox.switchBackgroundImagesTo('gif');
    }    
  },
  
  show: function(e) {
    e.stop();
		if (FancyZoomBox.zooming) return;
		FancyZoomBox.zooming   = true;
		var element            = e.findElement('a');
		var related_div        = element.content_div;
		var width              = (element.zoom_width || related_div.getWidth()) + 60;
		var height             = (element.zoom_height || related_div.getHeight()) + 60;
		var d                  = Window.size();
		var yOffset            = document.viewport.getScrollOffsets()[1];
		// ensure that newTop is at least 0 so it doesn't hide close button
		var newTop             = Math.max((d.height/2) - (height/2) + yOffset, 0);
		var newLeft            = (d.width/2) - (width/2);
		FancyZoomBox.curTop    = e.pointerY();
		FancyZoomBox.curLeft   = e.pointerX();
		FancyZoomBox.moveX     = -(FancyZoomBox.curLeft - newLeft);
		FancyZoomBox.moveY     = -(FancyZoomBox.curTop - newTop);
    FancyZoomBox.zoom.hide().setStyle({
			position	: 'absolute',
			top				: FancyZoomBox.curTop.px(),
			left			: FancyZoomBox.curLeft.px()
		});
    
		new Effect.Parallel([
			new Effect.Appear(FancyZoomBox.zoom, {sync:true}),
			new Effect.Move(FancyZoomBox.zoom, {x: FancyZoomBox.moveX, y: FancyZoomBox.moveY, sync: true}),
			new Effect.Morph(FancyZoomBox.zoom, {
			  style: {
			    width: width.px(),
			    height: height.px()
			  },
				sync: true,
				beforeStart: function(effect) {
				  // middle row height must be set for IE otherwise it tries to be "logical" with the height
    		  if (Prototype.Browser.IE) {
    		    FancyZoomBox.middle_row.invoke('setStyle', {height:(height-40).px()});
    		  }
					FancyZoomBox.fixBackgroundsForIE();
				},
				afterFinish: function(effect) {
				  FancyZoomBox.zoom_content.innerHTML = related_div.innerHTML;
					FancyZoomBox.unfixBackgroundsForIE();
					FancyZoomBox.zoom_close.show();
					FancyZoomBox.zooming = false;
				}
			})
		], { duration: 0.5 });
  },
  
  hide: function(e) {
    e.stop();
		if (FancyZoomBox.zooming) return;
		FancyZoomBox.zooming = true;		
		new Effect.Parallel([
			new Effect.Move(FancyZoomBox.zoom, {x: FancyZoomBox.moveX*-1, y: FancyZoomBox.moveY*-1, sync: true}),
			new Effect.Morph(FancyZoomBox.zoom, {
			  style: {
			    width: '1'.px(),
			    height: '1'.px()
			  },
				sync					: true,
				beforeStart: function(effect) {
					FancyZoomBox.fixBackgroundsForIE();
					FancyZoomBox.zoom_content.innerHTML = '';
					FancyZoomBox.zoom_close.hide();
				},
				afterFinish: function(effect) {
					FancyZoomBox.unfixBackgroundsForIE();
					FancyZoomBox.zooming = false;
				}
			}),
			new Effect.Fade(FancyZoomBox.zoom, {sync:true})
		], { duration: 0.5 });
  },
  
  // switches the backgrounds of the cells and the close image to png's or gif's
  // fixes ie's issues with fading and appearing transparent png's with 
  // no background and ie6's craptacular handling of transparent png's
  switchBackgroundImagesTo: function(to) {
    FancyZoomBox.cells.each(function(td) {
      var bg = td.getStyle('background-image').gsub(/\.(png|gif|none)\)$/, '.' + to + ')');
      td.setStyle('background-image: ' + bg);
    });
    var close_img = FancyZoomBox.zoom_close.firstDescendant();
    var new_img = close_img.readAttribute('src').gsub(/\.(png|gif|none)$/, '.' + to);
    close_img.writeAttribute('src', new_img);
  },
  
  // prevents the thick black border that happens when appearing or fading png in IE
	fixBackgroundsForIE: function() {
    if (Prototype.Browser.IE7) { FancyZoomBox.switchBackgroundImagesTo('gif'); }
	},
	
	// swaps back to png's for prettier shadows
	unfixBackgroundsForIE: function() {
    if (Prototype.Browser.IE7) { FancyZoomBox.switchBackgroundImagesTo('png'); }
	}
}

var FancyZoom = Class.create({
	initialize: function(element) {
	  this.options = arguments.length > 1 ? arguments[1] : {};
	  FancyZoomBox.init();
	  this.element = $(element);
		if (this.element) {
		  this.element.content_div = $(this.element.readAttribute('href').gsub(/^#/, ''));
  		this.element.content_div.hide();
  		this.element.zoom_width = this.options.width;
  		this.element.zoom_height = this.options.height;
      this.element.observe('click', FancyZoomBox.show);
		}
	}
});