<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs
		title="Discover things to do - Zvents"
		description="Discover things to do with concerts, events, theater, and festivals in hundreds of cities."
		author="Zvents, Inc."
		author_email="product@zvents.com"
		thumbnail="http://www.zvents.com/widgets/mapplet/zmap-thumbnail.png"
		screenshot="http://www.zvents.com/widgets/mapplet/zmap-screenshot.png"
		>
	<Require feature="sharedmap"/>
	<Require feature="dynamic-height"/>
	<Require feature="analytics"/>
</ModulePrefs>
<Content type="html"><![CDATA[

<!-- Zvents mapplet by Michael Geary -->
<!-- $Revision: 28415 $ - $Date: 2008-03-05 12:42:21 -0800 (Wed, 05 Mar 2008) $ -->

<style type="text/css">

/* netcal.css */

.ZventsWidget { font-family: arial, verdana, helvetica, sans-serif; }
.ZventsWidget { zoom: 1; } /* peekaboo bug */

.ZventsWidget *, .ZventsWidget a { margin: 0; padding: 0; text-align: left; }

.ZventsWidget a { text-decoration: none; }
.ZventsWidget a:hover { text-decoration: underline; }

.ZventsNavBarTitle { width: 99%; }

.ZventsNavBarTableRow .ZventsScrollUp { padding-left: 4px; }
.ZventsNavBarTableRow .ZventsScrollDown { padding-right: 4px; }
.ZventsNavBarTableRow .ZventsSpinner { padding: 4px; }

.ZventsScrollArrow * { display: block; }

.ZventsScrollArrowPad, .ZventsScrollArrowFill { overflow: hidden; display: block; font-size: 1px; }

.ZventsScrollArrowLink { cursor: pointer; }

.ZventsScrollArrowBody { display: block; border: 1px solid white; }
.ZventsScrollArrowBody { zoom: 1; }
a:hover .ZventsScrollArrowBody { border: 1px solid black; }

.ZventsScrollArrowFill { background-color: rgb(40,40,40); }

.ZventsEventList, .ZventsVenueList, .ZventsDetail { background-color: rgb(250,250,250); }
.ZventsListDay { position: relative; left: 0; top: 0; }
.ZventsDetail, .ZventsListDay { zoom: 1; }
.ZventsListDayWrapper, .ZventsResultsHeader, .ZventsDetail { padding: 4px; }
.ZventsEventListImage { float: right; position: relative; margin: 4px 2px; width: 66px; height: 66px; overflow: hidden; }
.ZventsEventListImage { zoom: 1; }
.ZventsCalendarDaySelected .ZventsCalendarDayContent, .ZventsListDayWrapper, .ZventsResultsHeader, /*.ZventsCalendarDayMouseOver*/.ZventsCalendarDayLink:hover .ZventsCalendarDayContent { background-color: rgb(200,200,200); }

.ZventsDetailName { font-size: 120%; font-weight: bold; }
.ZventsDetailTimeAtVenue { zoom: 1; }
.ZventsLabel { font-weight: bold; }

.ZventsVenueInfo { background-color: rgb(240,240,240); }
.ZventsVenueNameLink { font-size: 110% !important; font-weight: bold; }
.ZventsEventSeparator { overflow: hidden; height:1px; font-size:1px; background-color:rgb(230,230,230); }
.ZventsDetailWhenWhere { padding-bottom: 4px; margin-bottom: 4px; border-bottom: 1px solid rgb(210,210,210); }
.ZventsEventClear { clear: both; }

.ZventsCalendarFrame { background-color: white; border: 1px solid #888; cursor: default; }

.ZventsCalendarSides { border-top: 1px solid #888; }
.ZventsCalendarScrollable { cursor: default; overflow: hidden; }
.ZventsNavBarTitle { font-size: 110%; font-weight: bold; padding: 1px 0; }
.ZventsCalendarWeekTable { table-layout: fixed; width: 100%; height: 100%; border-collapse: collapse; /*padding: 1px;*/ }
.ZventsCalendarDayTable { width: 100%; }
.ZventsCalendarHeaderCell, .ZventsCalendarDayContent { font-size: .9em; }
.ZventsCalendarHeaderCell, .ZventsCalendarDayContent, .ZventsCalendarInlineDate  { padding: 0 2px; }
.ZventsCalendarHeaderTable { background-color: #E0E0E0; font-weight: bold; }

.ZventsCalendarWeekTable { table-layout: fixed; width: 100%; /*padding: 1px;*/ border: none; }

.ZventsCalendar, .ZventsCalendarWeeks { zoom: 1; } /* peekaboo bug */
.ZventsCalendarDayBody { zoom: 1; } /* peekaboo bug */

.ZventsCalendarDayLink { width: 100%; height: 100%; text-decoration: none; }
.ZventsCalendarDayLink * { display: block; }
.ZventsCalendarDayContent { padding: 2px; }

.ZventsCalendarDayPicker .ZventsCalendarDayBody, .ZventsCalendarDayPicker .ZventsCalendarDayBody * { display: block; }

.ZventsCalendarInlineDate { font-weight: bold; background-color: #F0F0F0; }
.ZventsCalendarDayInRange .ZventsCalendarInlineEvents .ZventsCalendarDayContent { border-style: none; }
/*.ZventsCalendarInlineEvents .ZventsEvent { border-bottom: 1px solid rgb(200,200,200); }*/

.ZventsLog { border: 2px solid rgb(192,192,192); overflow: scroll; }

.ZventsEventFrame .ZventsNavBar { display: none; }
.ZventsEventFrame .ZventsNavBar { border-bottom: 1px solid rgb(175,175,175); }

.ZventsDetailDescription p { margin-top: .5em; }

.ZventsDetailDescription { font-size: 90%; }
.ZventsDetailDescription { zoom: 1; } /* peekaboo bug */

.ZventsDetailImage { float: right; margin: 0 0 4px 4px; }

.ZventsCalendarDayLabelDayOfWeek { font-weight: normal; }

/* Background color and format for search widget */
.ZventsSearchTable /* , .ZventsSearchButtonTable */ { padding: 0 1px; }
.ZventsSearchTable { background-color: rgb(234,234,234); padding: 2px 4px; }
.ZventsSearchButtonTable { background-color: rgb(248,248,248); width: 100%; /*border-bottom: 1px solid rgb(175,175,175);*/ }
/*.ZventsNarrow .ZventsSearchButtonTable { border-bottom: none; }*/
.ZventsButton { margin: 2px; }
.ZventsSearchSpinner { width: 1%; padding: 2px; background-color: white; border-left: 1px solid rgb(175,175,175); }

.ZventsSearchInput { margin: 2px 0; }
.ZventsSearchLabel, .ZventsButtonSearch { font-weight: bold; }

/* Make search input text field full width */
.ZventsSearchTableCell, .ZventsSearchInput { width: 100%; }

.ZventsPager { width: 100%; text-align: center; padding: 2px 0 4px 0; font-weight: bold; }
.ZventsPagerHere { margin: 0 .7em; }

.ZventsMapDot { width: 20px; height: 17px; float: right; margin-top: 2px; margin-left: 4px; cursor:pointer; }
.ZventsNarrow .ZventsMapDot { float: left; margin-left:0; margin-right: 4px; }

.ZventsFilterFrame { padding: .5em; border: 1px solid rgb(175,175,175); cursor: default; }
.ZventsFilterTitle { font-weight: bold; }
.ZventsFilterListWrapper { margin: 0 .4em; }
.ZventsFilterSelected { font-weight: bold; }
.ZventsFilterCount { font-size: 90%; }
.ZventsFilterSeparator { height: 1em; }
.ZventsFilterList { margin-left: .7em; }
.ZventsFilterSeparator { height:3px; font-size:3px; }

.ZventsPopupCalendar { position: absolute; width: 175pt; }

/* Event image */
.ZventsImageLink { margin: 5px; }

/* Fix IE peekaboo bug in event list with images */
/* Hides from IE5-mac \*/
* html .ZventsEventWrapper { height: 1%; }
/* End hide from IE5-mac */

.ZventsViewMenuCurrent, .ZventsViewMenuSpacer { font-weight: bold; }

/* prevent internal float clearing from affecting container */
.ZventsFloatWrapper, .ZventsEventWrapper, .ZventsVenueWrapper {
	overflow: hidden;
	zoom: 1;
}

.ZventsCalendarDayBorderCol { width: 1px; }

/* Agenda view layout */
.ZventsAgendaTable { width: 100%; }
.ZventsAgendaDate, .ZventsAgendaTime { width: 0%; }
.ZventsAgendaEvent { width: 65%; }
.ZventsAgendaLocation { width: 35%; }
.ZventsAgendaDate, .ZventsAgendaTime, .ZventsAgendaEvent, .ZventsAgendaLocation { height: 100%; }
.ZventsAgendaCell { padding: 2px 4px; }
.ZventsAgendaDateDay { font-weight: bold; }
.ZventsAgendaTime * { text-align: right; }
.ZventsAgendaHeader .ZventsAgendaTime { text-align: center; }
.ZventsAgendaDayTo { font-size: 85%; }
.ZventsCalendarDayBorderRowCol, .ZventsAgendaDayBorderCol, .ZventsAgendaEventBorderCol { height: 1px; font-size: 1px; }

/* Agenda view colors */
.ZventsAgendaDate { background-color: #ECECEC; }
.ZventsAgendaTime { background-color: #F4F4F4; }
.ZventsAgendaEvent { background-color: #FFFFFF; }
.ZventsAgendaLocation { background-color: #FAFAFA; }
.ZventsCalendarDayBorderRowCol, .ZventsCalendarDayBorderCol, .ZventsAgendaDayBorderCol { background-color: #888; }
.ZventsAgendaEventBorderCol { background-color: #CCC; }
/*.ZventsAgendaTable { border-right: 1px solid #888; }*/
.ZventsAgendaDate, .ZventsAgendaTime, .ZventsAgendaEvent { border-right: 1px solid #E0E0E0; }

/*.ZventsWidget a { color:rgb(176,16,16); }*/

.ZventsClearFloat { clear: both; }

/* Keep at end of file */
.ZventsHider1 { display: block; }

/* Search.css */

.ZventsSearchFrame * { font-family: Arial,sans-serif; font-size: 10pt; }
form { margin: 0; padding: 0; }
a { text-decoration: none; }
a:hover { text-decoration: underline; }
.ZventsSearchTable { padding: 0 0 2px 0; background-color: white; width: 100%; }
.ZventsSearchButtonTable { background-color: white; width: 100%; }
.ZventsSearchButtonCellSearch { width: 100%; } 
.ZventsButton { margin: 2px 0; }
.ZventsSearchInput { margin: 2px 0; }
.ZventsSearchTableCell { width: 33%; }
.ZventsSearchInput { width: 100%; }
.ZventsNavBar { display: none; }
.ZventsCalendarSides, .ZventsCalendarFrame { border: none; }
/*.ZventsEventListName * { font-size: 110%; }*/
.ZventsVenueInfo { padding: 3px; }
.ZventsVenueWrapper { margin: 0 1px 0 0; border-bottom: 1px solid rgb(180,180,180); }
.ZventsEventWrapper, .ZventsMapInfoVenueMovie { margin: 2px 0 2px 4px; }
.ZventsMapInfoVenueMovieTitle, .ZventsEventNameLink { font-weight: bold; }
/*.ZventsToday { font-weight: bold; }*/

.ZventsQuickSearch {
	margin: 2px 2px 0 0;
	width: 300px;
}
.ZventsQuickSearch * {
	font-family: Arial,Helvetica,sans-serif;
	font-size: 10px;
}
.ZventsQuickSearch form {
	margin: 0;
	padding: 0;
}
.ZventsDayButton, .ZventsDayButton * {
	display: block;
	text-align: center;
}
.ZventsDayButton {
	color: #333;
	background-color: #f8f8f8;
	border: 1px solid #a0a0a0;
	border-top: 1px solid #d0d0d0;
	border-left: 1px solid #d0d0d0;
	text-decoration: none;
	cursor: pointer;
	line-height: 100%;
	font-weight: bold;
	margin-right: 2px;
	padding: 1px 1px 1px 1px;
}
.ZventsDayButtonDate {
	font-size: 15px;
}
.ZventsDayButtonDay {
	font-size: 11px;
}
#ZventsDayButtonAnyDay {
	margin-right: 0;
}
.ZventsDayButtonToday {
	color: #900810;
}
.ZventsDayButtonWeekend {
	background-color: #e0e0e0;
}
.ZventsDayButton:hover, .ZventsDayButtonSelected {
	background-color: #fff0cf;
	/*border: 1px solid #c2e1ef;*/
	/*color: #336699;*/
	text-decoration: none;
}

.overlabel-wrapper {
	position:relative;
	float:left;
	width: 100%;
	/*margin-right:3px;*/
}

label.overlabel {
	color:#999;
/*}*/
/*label.overlabel-apply {*/
	position:absolute;
	top:5px;
	left:5px;
	z-index:1;
	color:#777;
	cursor: text;
}

/* Make the document body take up the full window */
v\:* { behavior:url(#default#VML); }
html, body { width: 100%; height: 100% }
body { margin: 0; padding: 0; }
</style>

<script type="text/javascript">

var mapplet = location.host == 'gmodules.com';
if( mapplet ) {
	// Track the mapplet using Google Analytics
	_IG_Analytics( 'UA-31999-46', '/mapplet' );
}

// Scriptino
// Copyright 2007 Michael Geary
// Free beer and free speech license (MIT+GPL). Enjoy!

var _;
window.global = window;


Function.prototype.methods = function( methods ) {
	for( var name in methods || {} )
		this.prototype[name] = methods[name];
	return this;
};


Function.methods({
	extend: function( base, methods ) {
		this.prototype = Object.inherit( base.prototype );
		this.prototype.constructor = this;
		this.statics({ _base: base, _super: base.prototype });
		this.methods( methods );
	},
	
	statics: function( methods ) {
		for( var name in methods || {} )
			this[name] = methods[name];
		return this;
	},
	
	addMethods: function( methods ) {
		for( var name in methods || {} )
			if( ! this.prototype[name] )
				this.prototype[name] = methods[name];
		return this;
	},
	
	addStatics: function( methods ) {
		for( var name in methods || {} )
			if( ! this[name] )
				this[name] = methods[name];
		return this;
	}
});


Function.statics({
	replace: function( obj, methods ) {
		for( var name in methods ) {
			var old =  obj['_'+name] = obj[name];
			var method = obj[name] = methods[name];
			if( old.prototype ) old.prototype.constructor = method;
		}
	}
});


Object.statics({
	add: function( self ) {
		return Object.extendFromArray( self, arguments, false, 1 );
	},
	
	combine: function() {
		return Object.extendFromArray( {}, arguments, true, 0 );
	},
	
	copy: function( from ) {
		return Object.combine( from );
	},
	
	copyNamed: function( args, names ) {
		var p = {};
		for( var name in names )
			if( args[name] != null )
				p[name] = args[name];
		return p;
	},
	
	extendFromArray: function( self, array, always, start, stop ) {
		stop = stop || array.length;
		for( var i = start;  i < stop;  i++ ) {
			var obj = array[i];
			if( obj )
				for( var prop in obj )
					if( always  ||  self[prop] === undefined )
						self[prop] = obj[prop];
		}
		return self;
	},
	
	extend: function( self ) {
		return Object.extendFromArray( self, arguments, true, 1 );
	},
	
	inherit: function( o ) {
		function f() {}
		f.prototype = o;
		return new f;
	},
	
	isEmpty: function( o ) {
		if( o ) for( var i in o ) return false;
		return true;
	},
	
	sort: function( input, key, numeric ) {
		var sep = unescape('%uFFFF');
		
		var i = 0, n = input.length, sorted = [];
		if( numeric ) {
			if( typeof key == 'function' ) {
				for( ;  i < n;  ++i )
					sorted[i] = [ ( 1000000000000000 + key(input[i]) + '' ).slice(-15), i ].join(sep);
			}
			else {
				for( ;  i < n;  ++i )
					sorted[i] = [ ( 1000000000000000 + input[i][key] + '' ).slice(-15), i ].join(sep);
			}
		}
		else {
			if( typeof key == 'function' ) {
				for( ;  i < n;  ++i )
					sorted[i] = [ key(input[i]), i ].join(sep);
			}
			else {
				for( ;  i < n;  ++i )
					sorted[i] = [ input[i][key], i ].join(sep);
			}
		}
		
		sorted.sort();
		
		var output = [];
		for( i = 0;  i < n;  ++i )
			output[i] = input[ sorted[i].split(sep)[1] ];
		
		return output;
	}
});


String.methods({
	capitalize: function() {
		return this.charAt(0).toUpperCase() + this.slice(1);
	},
	
	htmlEscape: function() {
		var div = document.createElement( 'div' );
		div.appendChild( document.createTextNode(this) );
		return div.innerHTML;
	},
	
	htmlFix: function() {
		return this.unescapePlus().htmlEscape();
	},

	pad: function( n ) {
		return this.slice( 0, n );
	},
	
	trim: function() {
		return this.replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' );
	},
	
	// TODO: tag vs. wrap?
	tag: function( attrs, inner ) {
		var a = [];
		for( var name in attrs ) {
			var value = attrs[name];
			name = { Class:'class' }[name] || name;
			a.push( [ ' ', name, '="', value, '"' ].join() );
		}
		a = a.join();
		
		return( inner == null ? [
			'<', this, a, ' />'
		] : [
			'<', this, a, '>', inner, '</', this, '>'
		] ).join();
	},
	
	truncate: function( n, escape) {
		var len = this.length;
		if( typeof n != 'number'  ||  len <= n ) return this + '';
		var s = this.substring( 0, n + 1 ).replace( / +[^ ]+$/, '' ).replace( /[ :;,.]*$/, '' );
		if( escape ) s = s.htmlEscape();
		return s + String.ellipsis;
	},
	
	unescapePlus: function() {
		return unescape( this ).replace( /\+/g, ' ' );
	},
	
	//urlEscape: function() {
	//   return this.replace( /&/g, "&amp;" );
	//},
	
	unhash: function() {
		return this.replace( /^#/, '' );
	},
	
	//unquery: function() {
	//	return this.replace( /^\?/, '' );
	//},
	
	words: function( yields, delim ) {
		return this.split( delim != null ? delim : ' ' ).each( yields );
	},
	
	wrap: function( clas, tag ) {
		tag = tag || 'div';
		return [ '<', tag, ' class="', clas, '">', this, '</', tag, '>' ].join();
	}
});


String.statics({
	ellipsis: '&#8230;',
	enDash: '&#8211;',
	nbsp: '&#160;'
});


Array.addMethods({
	map: function( fn, that ) {
		var out = [];
		for( var i = 0, n = this.length;  i < n;  i++ ) {
			out.push( fn.call( that, this[i], i, this ) );
		}
		return out;
	}
});


Array.methods({
	// Return a compacted copy of an array, with null, undefined, and empty string elements removed
	compacts: function() {
		var a = [];
		for( var i = 0, n = this.length;  i < n;  i++ ) {
			var x = this[i];
			if( x != null && x != '' ) a[a.length] = x;
		}
		return a;
	},
	
	each: function( yields ) {
		var n = this.length, last = n - 1;
		var is = { first: true };
		for( var e = 0;  e < n;  e++ ) {
			if( e == last ) is.last = true;
			if( yields( this[e], is ) === false ) break;
			delete is.first;
		}
		return n;
	},
	
	index: function( key ) {
		var self = this;
		key = key || 'id';
		
		Object.extend( self, {
			by: {},
			
			push: function() {
				for( var i = 0, len = arguments.length;  i < len;  i++ ) {
					var item = arguments[i];
					var value = item[key];
					if( value ) this.by[value] = item;
					this[this.length] = item;
				}
				return this.length;
			}
		});
		
		self.each( function( item ) {
			var value = item[key];
			if( value ) self.by[value] = item;
		});
		
		return self;
	},
	
	pushif: function() {
		for( var i = 0, len = arguments.length;  i < len;  i++ ) {
			var item = arguments[i];
			if( item ) this.push( item );
		}
	}

});


Function.replace( Array.prototype, {
	join: function( sep ) {
		//return this._join( sep != null ? sep : '' );
		try {
			sep = sep != null ? sep : '';
			var s = this._join( sep );
			return s;
		}
		catch( e ) {
			//debugger;
		}
	}
});


Array.statics({
	of: function( a ) {
		return typeof a == 'object' && a.length != undefined ? a : [a];
	}
});


Object.extend( Math, {
	randomString: function( length, base ) {
		return Math.floor( Math.random() * Math.pow(base,length) ).toString( base );
	},

	roundDownOdd: function( value ) {
		return Math.floor( value - 1 ) | 1;
	}
});


Number.methods({
	pad: function( n ) {
		return n == null ? this : ( 1000000000 + this + '' ).slice( -n );
	},
	
	toFixed: function( digits, round ) {
		var value = this + 0;
		if( round ) value += Math.pow( 10, -digits ) / 2;
		var s = '' + value;
		var iDec = s.indexOf('.') + 1;
		if( ! iDec ) { s += '.';  iDec = s.length; }
		for( var zeroes = s.length - iDec + digits;  zeroes > 0;  zeroes-- ) { s += '0'; }
		return s.slice( 0, iDec + digits );
	}
});

// Scriptino Date

Function.replace( global, {
	Date: function( time ) {
		var date;
		try {
			if( arguments.length > 1 )
				return new _Date( _Date.UTC.apply( global, arguments ) );
			
			if( ! time )
				return Date.dateNow();
			
			if( typeof time == 'number' ) {
				if( time < 100000000000 ) time *= 1000;  // handle time in seconds or milliseconds
				return new _Date( time );
			}
			
			if( typeof time == 'object'  &&  time.constructor == Date )
				return new _Date( time.getTime() );
			
			if( typeof time != 'string' )
				return Date.dateNow();
			
			time = time.trim();
			
			// Wed Jan 31 12:30:45 GMT 2007
			var m = time.match( /^[a-z]{3} ([a-z]{3}) (\d{2}) (\d{2}):(\d{2}):(\d{2}) \w{3} (\d{4})$/i );
			if( m ) return new Date( Date.UTC( +m[6], Date.numberFromShortMonth(m[1]), +m[2], +m[3], +m[4], +m[5] ) );
			
			// Jan 2, 2007
			var m = time.match( /^([a-z]+)\s*(\d+)\s*,?\s*(\d+)$/i );
			if( m ) {
				var mon = Date.numberFromShortMonth( m[1] );
				if( mon != null )
					return new Date( Date.UTC( +m[3], mon, +m[2] ) );
			}
			
			// 2007-01
			// 2007-01-31 
			// 2007-01-31 12:30
			// 2007-01-31 12:30:45
			var m = time.match( /^(\d{4})-(\d{2})(-(\d{2})( (\d{2}):(\d{2})(:(\d{2}))?)?)?$/ );
			if( m ) return new Date( Date.UTC( +m[1], m[2]-1, +m[4] || 1, +m[6] || 0, +m[7] || 0, +m[9] || 0 ) );
			
			// 20070131
			var m = time.match( /^(\d{4})(\d{2})(\d{2})$/ );
			if( m ) return new Date( Date.UTC( m[1], m[2]-1, m[3] ) );
		}
		catch( e ) {
			return Date.dateNow();
		}
	}
});


Date.statics({
	parse: _Date.parse,
	UTC: _Date.UTC,
	
	oneSecond: 1000,
	oneMinute: 1000 * 60,
	oneHour: 1000 * 60 * 60,
	oneDay: 1000 * 60 * 60 * 24,
	oneWeek: 1000 * 60 * 60 * 24 * 7,
	
	seconds: function( n ) { return n * Date.oneSecond; },
	minutes: function( n ) { return n * Date.oneMinute; },
	hours: function( n ) { return n * Date.oneHour; },
	days: function( n ) { return n * Date.oneDay; },
	weeks: function( n ) { return n * Date.oneWeek; },
	
	dayNames: [
		'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
	],
	
	monthNames: [
			'January', 'February', 'March', 'April', 'May', 'June',
			'July', 'August', 'September', 'October', 'November', 'December'
	],
	
	// Do the same as "new Date()", but treat time as local/UTC
	dateNow: function() {
		var date = new _Date;
		return new _Date( date.getTime() - date.getTimezoneOffset() * Date.oneMinute );
	},
	
	now: function() {
		return Date.dateNow().getTime();
	},
	
	today: function() {
		return Date().midnight();
	},
	
	longDayFromShortDay: function( shortDay ) {
		var days = { Sun:0, Mon:1, Tue:2, Wed:3, Thu:4, Fri:5, Sat:6 };
		return Date.dayNames[ days[shortDay] ];
	},
	
	numberFromShortMonth: function( shortMonth ) {
		var months = {
			Jan:0, Feb:1, Mar:2, Apr:3, May:4, Jun:5,
			Jul:6, Aug:7, Sep:8, Oct:9, Nov:10, Dec:11
		};
		return months[shortMonth];
	},
	
	longMonthFromShortMonth: function( shortMonth ) {
		return Date.monthNames[ Date.numberFromShortMonth(shortMonth) ];
	},
	
	formatLongDateRange: function( first, last ) {
		first = Date( Date(first).midnight() );
		last = Date( last ? Date(last).midnight() : first );
		return (
			first.getFullYear() != last.getFullYear() ? [
				first.format('{January} {D}, {Y}'), last.format('{January} {D}, {Y}')
			] :
			first.getMonth() != last.getMonth() ? [
				first.format('{January} {D}'), last.format('{January} {D}, {Y}')
			] :
			first.getDate() != last.getDate() ? [
				first.format('{January} {D}'), last.format('{D}, {Y}')
			] : [
				first.format('{Sunday}, {January} {D}, {Y}')
			]
		).join( String.enDash );
	},
	
	formatTimeRange: function( first, last ) {
		return Date(first).hmm() + ( last ? String.enDash + Date(last).hmm() : '' );
	}
});


_Date.methods({
	time: function() {
		return this.getTime();
	},
	
	midnight: function() {
		return Date(this).setHours( 0, 0, 0, 0 );
	},
	
	addDaysAtMidnight: function( days ) {
		time = this.midnight();
		if( typeof days == 'function' ) days = days( time );
		var date = Date( time );
		return date.setDate( date.getDate() + days );
	},
	
	addDaysExact: function( days ) {
		time = this.getTime();
		return this.addDaysAtMidnight() + ( time - this.midnight() );
	},
	
	changeDay: function( date ) {
		return Date(date).midnight() + ( this.time() - this.midnight() );
	},
	
	nextDay: function() {
		return this.addDaysAtMidnight( 1 );
	},
	
	prevDay: function() {
		return this.addDaysAtMidnight( -1 );
	},
	
	nextWeek: function() {
		return this.addDaysAtMidnight( 7 );
	},
	
	prevWeek: function() {
		return this.addDaysAtMidnight( -7 );
	},
	
	beginWeek: function() {
		return this.beginPeriod( 'getDay', 0 );
	},
	
	nextMonth: function() {
		var date = Date( this.beginMonth() );
		return date.setMonth( date.getMonth() + 1 );
	},
	
	prevMonth: function() {
		var date = Date( this.beginMonth() );
		return date.setMonth( date.getMonth() - 1 );
	},
	
	beginMonth: function() {
		return this.beginPeriod( 'getDate', 1 );
	},
	
	beginPeriod: function( getter, first ) {
		var self = this;
		return self.addDaysAtMidnight(
			function( time ) { return first - self[getter]() }
		);
	},
	
	isWeekend: function() {
		switch( this.getDay() ) { case 0: case 6: return true; }
	},
	
	isToday: function() {
		return this.midnight() == Date.today();
	},
	
	formatLongDate: function( weekDay, relative ) {
		if( relative ) {
			if( this.isToday() ) return '<span class="ZventsToday">Today</span>';
		}
		return this.format(
			( weekDay ? '{Sunday}, ' : '' ) +
			'{January} {D}, {Y}'
		);
	},
	
	firstFullWeekOfMonth: function() {
		var week = this.firstWeekOfMonth(), date = Date(week);
		return date.getMonths() == this.getMonths() ?
			week : date.addDaysAtMidnight( 7 );
	},
	
	firstWeekOfMonth: function() {
		return Date( this.beginMonth() ).beginWeek();
	},
	
	format: function( str ) {
		var date = this;
		return str.replace( /{(\w+)(:(\d+))?}/g,
			function( match, code, x, arg ) {
				return date[code] ? date[code](arg) : match;
			}
		);
	},
	
	// Formatters
	a: function() { return this.am().slice(0,1); },
	am: function() { return this.getHours() < 12 ? 'am' : 'pm'; },
	D: function() { return this.getDate(); },
	DD: function() { return this.D().pad(2); },
	h: function() { return ( ( this.getHours() + 11 ) % 12 + 1 ); }, 
	//hh: function() { return this.h().pad(2); },
	//h24: function() { return this.getHours(); }, 
	//hh24: function() { return this.h24().pad(2); },
	hmm: function() { return this.format( '{h}:{mm}&#160;{am}' ); },
	Jan: function() { return this.January().slice(0,3); },
	January: function() { return Date.monthNames[ this.getMonth() ]; },
	m: function() { return this.getMinutes(); },
	mm: function() { return this.m().pad(2); },
	M: function() { return this.getMonth() + 1; },
	MM: function() { return this.M().pad(2); },
	MDY: function() { return this.format( '{M}/{D}/{Y}' ); },
	s: function() { return this.getSeconds(); },
	ss: function() { return this.s().pad(2); },
	Sun: function() { return this.Sunday().slice(0,3); },
	Sunday: function() { return Date.dayNames[ this.getDay() ]; },
	Y: function() { return this.getFullYear() },
	YMD: function() { return this.format( '{Y}-{MM}-{DD}' ); }
});

// We keep time in an unusual way: We pretend that UTC is our local time. For example, our
// "midnight" is midnight UTC, not midnight in your local time zone. This matches the server
// and lets us do time zone independent date searches. So, we replace all Date.getFoo() and
// Date.setFoo() methods with the corresponding Date.getUTCFoo() and Date.setUTCFoo().

new function() {
	var methods = _Date.prototype;
	var names = { Date:1, Day:1, FullYear:1, Hours:1, Milliseconds:1, Minutes:1, Month:1, Seconds:1 };
	for( var act in { get:1, set:1 } ) {
		for( var name in names ) {
			var utc = methods[act+'UTC'+name];
			if( utc ) methods[act+name] = utc;
		}
	}
};

// Scriptino DOM

global.jQuitoData = {};


function $() {
	return new $.$( arguments );
};


(function() {
	var ua = navigator.userAgent.toLowerCase();
	var b = $.browser = {
		iphone: ver('iphone'),
		opera: ver('opera'),
		//konq: ver('konqueror'),
		opera: ver('opera'),
		safari: ver('safari'),
		webkit: ver('applewebkit')
	};
	var ie = b.msie = ! b.opera  &&  ver('msie');
	b.iePngHack = ie >= 5.5  &&  ie < 7.0  &&  ! b.opera;
	b.mozilla = ! b.webkit  &&  ! /compatible/.test(ua)  &&  ver('mozilla');
	
	function ver( name ) {
		var i = ua.indexOf( name );
		if( i < 0 ) return false;
		i += name.length + 1;
		var dots = 0;
		for( var j = i;  use(ua.charAt(j));  j++ ) {}
		return +ua.substring( i, j ) || true;
		
		function use( c ) {
			return c == '.' ? dots++ == 0 : c >= '0' && c <= '9';
		}
	}
})();


$.statics({
	window: window,
	document: document,
	
	addScript: function( url, doc ) {
		//$('head',document).append(
		//	$.SCRIPT({ type:'text/javascript', charset:'utf-8', src:url })
		//);
		doc = doc || document;
		var script = doc.createElement( 'script' );
		script.type = 'text/javascript';
		script.charset = 'utf-8';
		script.src = url;
		$.headOrBody(doc).appendChild( script );
		return script;
	},
	
	args: function( key, val, yields ) {
		switch( typeof key ) {
			case 'string':
				yields( key, val );
				break;
			case 'object':
				for( var k in key )
					yields( k, key[k] );
				break;
		}
	},
	
	// Return an element's bounding box in pixels, relative to a root element or the document
	// TODO: bounding box for multiple elements
	box: function( e, root ) {
		if( ! e ) return;
		var x = 0, y = 0, cx = e.offsetWidth, cy = e.offsetHeight;
		if( e.nodeType != 1 ) e = e.parentNode;
		while( e ) {
			x += e.offsetLeft;
			y += e.offsetTop;
			e = e.offsetParent;
		}
		if( root ) {
			var boxRoot = $.box( root );
			x -= boxRoot.x;
			y -= boxRoot.y;
		}
		return { x:x, y:y, cx:cx, cy:cy };
	},
	
	hasClass: function( e, c ) {
		return new RegExp( '(^|\\s)' + c + '(\\s|$)' ).test( e.className || '' );
	},
	
	headOrBody: function( doc ) {
		var h = doc.getElementsByTagName( 'head' );
		return h && h[0] || doc.body;
	},
	
	mouseunframe: function( e ) {
		var mouse = { x: e.clientX, y: e.clientY };
		var doc = e.srcElement.ownerDocument;
		var win = doc.parentWindow || doc.defaultView;
		var frame = win.frameElement;
		if( frame ) {
			var bounds = $.box( frame );
			mouse.x += bounds.x;
			mouse.y += bounds.y;
		}
		return mouse;
	},
	
	parent: function( e, tag, top ) {
		while( e && e != top ) {
			if( e.tagName.toLowerCase() == tag )
				return e;
			e = e.parentNode;
		}
	},
	
	viewport: function() {
		var e = $.document.documentElement || {},
			b = $.document.body || {},
			w = $.window;
		
		return {
			x: w.pageXOffset || e.scrollLeft || b.scrollLeft || 0,
			y: w.pageYOffset || e.scrollTop || b.scrollTop || 0,
			cx: min( e.clientWidth, b.clientWidth, w.innerWidth ),
			cy: min( e.clientHeight, b.clientHeight, w.innerHeight )
		};
		
		function min() {
			var v = Infinity;
			for( var i = 0;  i < arguments.length;  i++ ) {
				var n = arguments[i];
				if( n && n < v ) v = n;
			}
			return v;
		}
	}
});


$.$ = function( args ) {
	var self = this;
	var win = $.window;
	
	function add( a ) {
		switch( typeof a ) {
			case 'string':
				var _id = a.split( '#' ), id = _id[1];
				if( id ) {
					var e = win.document.getElementById( id );
					if( e ) self[self.length++] = e;
				}
				break;
			default:
				self[self.length++] = a;
				break;
		}
	}
	
	self.length = 0;
	self.handlers = {};
	
	for( var i = 0, iN = args.length;  i < iN;  i++ ) {
		var all = args[i];
		if( all && all.window ) {
			if( i == iN - 1 )
				add( all );
			else
				win = all;
		}
		else if( typeof all == 'string' ||  all.length == null ) {
			add( all );
		}
		else {
			for( var j = 0, jN = all.length;  j < jN;  j++ )
				add( all[j] );
		}
	}
	
	self.e = self[0];
};


$.$.prototype = {
	addClass: function( c ) {
		return this.each( function( e ) {
			if( ! $.hasClass( e, c ) ) {
				var n = e.className;
				e.className = n ? n + ' ' + c : c;
			}
		});
	},
	
	css: function( styles ) {
		return this.each( function( e ) {
			var es = e.style;
			for( var name in styles ) {
				var style = styles[name];
				es[name] = style;
			}
		});
	},
	
	each: function( yields ) {
		for( var i = 0, n = this.length;  i < n;  i++ )
			yields( this[i], i );
		return this;
	},
	
	each$: function( yields ) {
		for( var i = 0, n = this.length;  i < n;  i++ ) {
			var e = this[i];
			yields( $(e), e, i );
		}
		return this;
	},

	// TODO: use $.anchor?
	findParent: function( top, patterns ) {
		for( var e = this.e;  e && e != top;  e = e.parentNode ) {
			var c = e.className;
			for( var type in patterns ) {
				var m = c.match( patterns[type] );
				if( m ) { var r = {};  r[type] = m[1];  return r; }
			}
		}
	},
	
	hasClass: function( c ) {
		var has = false;
		this.each( function( e ) {
			if( $.hasClass( e, c ) ) {
				has = true;
				return false;
			}
		});
		return has;
	},
	
	hide: function() {
		return this.each( function( e ) {
			e.style.display = 'none';
		});
	},
	
	html: function( html ) {
		this.off();
		return this.each( function( e ) {
			e.innerHTML = html;
		});
	},
	
	on: function( type, f, args ) {
		if( this.handlers[type] ) return this;
		var handler = this.handlers[type] = $.event.handler( type, f );
		if( handler.custom ) {
			handler = handler.custom( this, f, args || {} );
			handler.on && handler.on();
			return this;
		}
		return this.each( function( e ) {
			if( e.addEventListener )
				e.addEventListener( type, handler.dom, false );
			else if( e.attachEvent )
				e.attachEvent( 'on' + type, handler.dom );
		});
	},
	
	off: function() {
		var self = this;
		var n = arguments.length;
		if( n ) for( var i = 0;  i < n;  i++ ) off( arguments[i] );
		else for( type in self.handlers ) off( type );
		return self;
		
		function off( type ) {
			var handler = self.handlers[type];
			if( ! handler ) return;
			delete self.handlers[type];
			if( handler.off ) {
				handler.off();
				return;
			}
			self.each( function( e ) {
				if( e.removeEventListener )
					e.removeEventListener( type, handler.dom, false );
				else if( e.detachEvent )
					e.detachEvent( 'on' + type, handler.dom );
			});
		}
	},
	
	remove: function() {
		return this.each( function( e ) {
			e.parentNode && e.parentNode.removeChild( e );
		});
	},
	
	box: function( root ) {
		return $.box( this.e, root );
	},
	
	isIn: function() {
		var child = this.e;
		var len = arguments.length;
		while( child ) {
			for( var i = 0;  i < len;  i++ )
				if( child == arguments[i] ) return true;
			child = child.parentNode;
		}
		return false;
	},
	
	hitTest: function( xIn, yIn ) {
		for( var i = 0, len = this.length;  i < len;  i++ ) {
			var e = this[i];
			var box = $.box( e );
			// TODO: refactor
			var x = xIn - box.x;
			var y = yIn - box.y;
			if( x >= 0 && x < box.cx && y >= 0 && y < box.cy ) {
				return { element:e, x:x, y:y };
			}
		}
	},
	
	parent: function( tag, top ) {
		return $.parent( this.e, tag, top );
	},
	
	remove: function() {
		return this.each( function( e ) {
			e.parentNode.removeChild( e );
		});
	},
	
	removeClass: function( c ) {
		return this.each( function( e ) {
			var classes = e.className.split(' ');
			for( var i = 0, n = classes.length;  i < n;  i++ ) {
				if( classes[i] == c ) {
					classes.splice( i, 1 );
					e.className = classes.join(' ');
					break;
				}
			}
		});
	},
	
	show: function() {
		return this.each( function( e ) {
			e.style.display = 'block';
		});
	}
};


$.methods = function( methods ) {
	return $.$.methods( methods );
};


$.event = {
	handler: function( type, f, args ) {
		var custom = $.event.custom[type];
		return custom ? {
			custom: custom
		} : {
			dom: function( e ) {
				e = e || $.window.event;
				if( ! e.target )
					e.target = e.currentTarget || e.srcElement;
				if( f(e) === false ) {
					if( e.preventDefault ) {
						e.preventDefault();
						e.stopPropagation();
					}
					else {
						e.cancelBubble = true;
					}
					return false;
				}
			}
		};
	},
	
	custom: {
		hover: function( $q, f, args ) {
			var timer;
			var timeIn = args.timeIn != null ? args.timeIn : 250;
			
			function clear() {
				clearTimeout( timer );
				timer = null;
			}
			
			return {
				on: function() {
					$q.on( 'over', function( e, over ) {
						if( over ) {
							if( timer ) {
								clear();
							}
							timer = setTimeout( function() {
								timer = null;
								f( e, true );
							}, timeIn );
						}
						else {
							clear();
							f( e, false );
						}
					});
				},
				
				off: function() {
					clear();
					$q.off( 'over' );
				}
			};
		},
		
		over: function( $q, f, args ) {
			return {
				on: function() {
					$q.on( 'mousemove', function( e ) {
						f( e, true );
					})
					.on( 'mouseleave', function( e ) {
						f( e, false );
					});
				},
				
				off: function() {
					$q.off( 'mousemove' ).off( 'mouseleave' );
				}
			};
		}
	}
};

// overlabel.js

(function() {
	
	function get( label ) {
		var id = label.htmlFor || label['for'];
		var $label = $(label);
		var $input = $( '#' + id )
		return {
			id: id,
			label: label,
			$label: $label,
			$input: $input,
			input:$input[0],
			show: function( yes ) {
				$label.css({ textIndent: yes ? '0px' : '-1000px' });
			}
		};
	}
	
	$.$.methods({
		overlabel: function() {
			this.each( function( label ) {
				var my = get( label );
				if( my.input.value !== '' )
					my.show( false );
				my.$input.on( 'focus', function() {
					my.show( false );
				}).on( 'blur', function() {
					if( my.input.value === '' )
						my.show( true );
				});
				//my.$label.on( 'click', function() {
				//	my.$input.e.focus();
				//});
			});
		},
		
		getlabelvalue: function() {
			var value;
			this.each( function( label ) {
				var my = get( label );
				value = my.input.value;
			});
			return value;
		},
		
		setlabelvalue: function( value ) {
			this.each( function( label ) {
				var my = get( label );
				my.show( value === '' );
				my.input.value = value;
			});
		}
	});
})();


// hoverize.js

(function() {
	
	var opt = {
		slop: 7,
		interval: 200
	};
	
	function $body() {
		return $body.b = $body.b || $(document.body);
	}
	
	function start() {
		if( ! timer ) {
			timer = setInterval( check, opt.interval );
			$body().on( 'mousemove', move );
		}
	}
	
	function clear() {
		if( timer ) {
			clearInterval( timer );
			timer = null;
			$body().off( 'mousemove' );
		}
	}
	
	function check() {
		if ( ( Math.abs( cur.x - last.x ) + Math.abs( cur.y - last.y ) ) < opt.slop ) {
			clear();
			for( var i  = 0,  n = functions.length;  i < n;  ++i )
				functions[i]();
		}
		else {
			last = cur;
		}
	}
	
	function move( e ) {
		cur = { x:e.screenX, y:e.screenY };
	}
	
	var timer, last = { x:0, y:0 }, cur = { x:0, y:0 }, functions = [];
	
	hoverize = function( fn ) {
		
		function fire() {
			clear();
			return fn.apply( null, args );
		}
		functions.push( fire );
		
		var args;
		
		return {
			clear: clear,
			
			now: function() {
				args = arguments;
				fire();
			},
			
			hover: function() {
				args = arguments;
				start();
			}
		};
	}
})();

</script>

<script type="text/javascript">

// Load Google Maps API

if( ! mapplet )
	document.write(
		'<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=' +
		{
			'gigapad': 'ABQIAAAA-iPwAvJ-2mqMvNMYzG-auBTCDaeoAnk9GZSdGi854AcXbJXoXRQXyyisbtOZ7YAtSeeDEIFZf9MlQw',
			'mg.to': 'ABQIAAAAgNQJhbWKFHRJJiHCXotPZxQjDwyFD3YNj60GgWGUlJCU_q5i9hRnvuPxZd7ID_efv8JjDf5c7cLpFw',
			'www.zvents.com': 'ABQIAAAAO_CBwOdRZVSaTRWgDcZw5hSKgo1qQ_lsrpmk64D0htM8hhd94RQhsNA2kG56Q7LdFPFCeU70Kr6Fqw'
		}[location.host] +
		'" type="text/javascript"><\/script>' );

</script>

<script type="text/javascript">

// netcal.js - Zvents Network Calendar
// Copyright 2006 Zvents, Inc.

// The main Zvents function

function Zvents( opt ) {
	return Z.NetCal( opt );
}

Z = Zvents;
Z.baseUrl = 'http://www.zvents.com/';
Z.imageBaseUrl = 'http://images.zvents.com/';

Z.NetCal = function( opt ) {
	Z.$ = $;
	Z.opt = opt;
	
	Object.add( Z.opt, {
		inline: true,
		json: false,
		render: {},
		views: {
			menu: [ 'all', 'month', 'week', 'day' ],
			initial: 'month'
		},
		widgets: {},
		window: parent
	});
	
	Object.add( Z.opt.widgets, {
		calendar: {},
		nearby: {},
		search: {}
	});

	Z.opt.scope = Z.opt.views.initial;
	
	// Find this .js file and compute base URLs
	var scripts = document.getElementsByTagName( 'script' );
	var script = scripts[ scripts.length - 1 ];
	var src = script.src;
	var i = src.search( /\/[^\/]+\.js$/ ) + 1;
	Z.scriptBaseUrl = src.slice( 0, i );
	Z.jsFileName = src.slice( i );
	
	Z.jsFileName = Z.jsFileName || 'netcal.js';
	
	Z.preloadImages(
		'zbutton.gif'
	);
	
	var viewed;
	'event venue month week day all'.words( function( view ) {
		var value = Z.opt[view];
		if( value ) {
			views[view]( value );
			viewed = true;
			return false;
		}
	});
	
	if( ! viewed )
		views[ Z.opt.scope || 'month' ]( Date.today() );
};


Z.metersPerMile = 1609.344;
Z.milesPerDegree = 69.172;


Z.timeClass = function( name, time ) {
	return [ name, Date(time).YMD() ].join('-');
};

Z.copyParams = function( args ) {
	return Object.copyNamed( args, Z.paramNames );
};

Z.ready = function( ids, yields ) {
	ids = Array.of( ids );
	if( ready() ) return;
	var interval = setInterval( ready, 50 );
	
	function ready() {
		for( var i = 0;  i < ids.length;  i++ ) {
			var id = ids[i];
			if( ! id ) continue;
			var e = $.document.getElementById( id );
			if( ! e ) return;
		}
		if( interval ) clearInterval( interval );
		yields();
		return true;
	}
};


Z.json = Z.json || {};
Z.urlParams = Z.urlParams || {};


// URL parameters


Z.paramNames = {
	all:1, cat:1, cuisine:1, date:1, day:1, days:1, endtime:1, event:1, limit:1, month:1, offset:1,
	price:1, radius:1, search:1, sort:1, st:1, venue:1, week:1, what:1, when:1, where:1
};


Z.preloadImages = function() {
	var images = [];
	for( var i = 0;  i < arguments.length;  i++ ) {
		var img = new Image;
		img.src = [ Z.imageBaseUrl, 'images/', arguments[i] ].join();
		images[images.length] = img;
	}
};


Z.htmlFix = function( str ) {
	return ( str == null ? '' : '' + str ).htmlFix();
};

//Z.urlEscape = function( str ) {
//   return ( '' + str ).urlEscape();
//};

Z.fullFormat = function( text ) {
	return Z.autoLinkUrls( Z.autoLinkEmailAddresses( Z.miniFormat( text ) ) );
};

Z.miniFormat = function( text ) {
	return Z.simpleFormat( text.htmlEscape() );
};

Z.simpleFormat = function( str ) {
	var p = '<p class="ZventsParagraph">', pEnd = '</p>';
   return [
		p,
			str.replace( /(\r\n|\n|\r)/g, "\n" )
				.replace( /\n\n+/g, "\n\n" )
				.replace( /\n\n/g, pEnd + p )
		      .replace( /([^\n])(\n)([^\n])/g, '$1$2<br class="ZventsBreak" />$3' ),
		pEnd
	].join();
};

Z.autoLinkUrls = function( str ) {
	var re = Z.autoLinkUrls.re;
	if( ! re ) return str;
	var out = [];
	var m;
	var last = 0;
	while( ( m = re.exec(str) ) ) {
		out[out.length] = str.slice( last, m.index );
		out[out.length] = format( m[0], m[1], m[2], m[3], m[4], m[5] );
		last = re.lastIndex;
	}
	out[out.length] = str.slice( last );
	return out.join();

	function format( all, a, b, c, d, e ) {
		if( a.search( /<a\s/i ) >= 0 ) return all;  // don't replace URL's that are already linked
		var text = b + c;
		if( b == "www." ) b = "http://www.";
		return [ a, '<a class="ZventsSiteLink" href="', Z.linkToSite(b+c), '">', text, '</a>', e ].join();
	}
};

if( ! $.browser.msie  ||  $.browser.msie >= 5.5 )
	Z.autoLinkUrls.re = new RegExp( '(<\\w+.*?>|[^=!:' + "'" + '"\\/]|^)((?:http[s]?:\\/\\/)|(?:www\\.))(([\\w]+[=?&\\/.-]?)*\\w+[\\/]?(?:\\#\\w*)?)([^\\w0-9A-Za-z]|\\s|<|$)', 'g' );

Z.autoLinkEmailAddresses = function( str ) {
   return str.replace(
      /([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/g,
      '<a class="ZventsEmailLink" href="mailto:$1">$1</a>' );
};


// Simple JSON

Z.Json = {
	load: function( a ) {
		var spin = Z.opt.spin;
		spin && spin( true );
		var url =
			a.params ? Z.Json.url( a.name, a.params ) :
			a.args ? Z.Json.argsUrl( a.name, a.args, a.custom ) :
			a.url;
		global[a.name] = function( json ) {
			function callback( j ) { json = j; }
			eval( json );
			try {
				// Ignore response if another one is about to replace it
				if( Z.lastJson != json.rsp.content.search_info.sid.split('.')[1] )
					return;
			}
			catch(e ) {}
			a.yields( json );
			spin && spin( false );
		};
		
		$.addScript( url );
	},
	
	url: function( name, params ) {
		if( params.e || params.t || params.u || params.v ) {
			// Clean out unrelated params for event/tag/user/venue requests
			for( var p in { date:1, endtime:1, limit:1, offset:1, radius:1, search:1, what:1, when:1, where:1 } )
				delete params[p];
		}
		var controller = params.controller;
		delete params.controller;
		params.jsonsp = name;
		Z.lastJson = ( Z.lastJson || 0 ) + 1;
		return [ Z.url( Z.baseUrl + ( controller || 'json' ), params ), '&sid=', Z.session, '.', Z.lastJson ].join();
	},
	
	argsUrl: function( name, args, custom ) {
		var params = Object.combine(
			{
				cat: args.cat,
				cuisine: args.cuisine,
				price: args.price,
				date: argtime( args.date ),
				endtime: argtime( args.endtime ),
				extended: !! args.description,
				limit: args.limit || 10,
				offset: args.offset || 0,
				radius: args.radius,
				st: args.st,
				e: args.event,
				t: args.tag,
				u: args.user,
				v: args.venue
			},
			args.ads && {
				ads: 1
			},
			/*args.search &&*/ {
				search: 'true',
				what: args.what,
				when: args.when,
				where: args.where
			},
			args.st != 'venue' && {
				g: args.group
			},
			args.images && {
				image_urls: 'true'
			},
			custom
		);
		if( Z.opt.json ) {
			for( var p in Z.opt.json ) {
				var q = Z.opt.json[p];
				if( q === true ) q = Z.opt[p];
				params[p] = q;
			}
		}
		return Z.Json.url( name, params )
	}
};


Z.Cookie = {
	read: function( name ) {
		var match = $.document.cookie.match( new RegExp( name + '=([^;]+)' ) );
		return match && unescape( match[1] );
	},
	
	write: function( name, value ) {
		$.document.cookie = name + '=' + escape( value );
	}
};

Z.session = ( function() {
	var name = 'Zvents';
	var value = Z.Cookie.read( name );
	if( ! value ) {
		value = Math.randomString( 10, 36 );
		Z.Cookie.write( name, value );
	}
	return value;
})();

// End Zvents common code


// Zvents calendar objects

Z.render = function( name, args, object ) {
	// Uncomment this line for render trace:
	//window.console && console.debug( 'Z.render', name, args, object );
	
	args = args || {};
	return ( args.render && args.render[name] || Z.renderStock[name] )( args, object );
};

Z.renderStock = {
	listFrame: function( args, html ) {
		return [
			'<div class="ZventsWidget ZventsEventFrame">',
				//renderNavBar(),
				'<div class="ZventsEventList">',
					html,
				'</div>',
			'</div>'
		].join();
	},
	
	divDate: function( args, date ) {
		return [
			'<div class="ZventsListDay ', Z.timeClass("ZventsListDay",date), '">',
				args.inlineDate ? '' :
				'<div class="ZventsListDayWrapper">' +
					Z.render( 'dayHeader', args, date ) +
				'</div>',
			'</div>'
		].join();
	},
	
	dayHeader: function( args, date ) {
		return [
			'<div class="ZventsListDayHeader">',
					Date(date).formatLongDate( true ),
			'</div>'
		].join();
	},
	
	resultsHeader: function( args, html ) {
		return [
			'<div class="ZventsResultsHeader">',
				html,
			'</div>'
		].join();
	},

	error: function( args, error ) {
		return error;
	},
	
	venues: function( args, venues ) {
		var html =
			venues.error ? [ Z.render( 'error', args, venues.error ) ] :
			renderSearch();
		return Z.render( 'listFrame', args, html.join() );
		
		function renderSearch() {
			return [
				Z.render( 'resultsHeader', args,
					venues.length ? '' : 'No events found' ),
				venues.map( function( venue ) {
					return Z.render( 'venue', args, venue );
				}).join(),
				Z.render( 'pager', args )
			];
		}
		
		//function renderDays() {
		//	var outer = [];
		//	events.days.each( function( day ) {
		//		outer.push( Z.render( 'divDate', args, day.date ) );
		//		outer.push( day.events.map( function( event ) {
		//			return renderEvent( event );
		//		}).join() );
		//	});
		//	
		//	return [
		//		outer.join(),
		//		Z.render( 'pager', args )
		//	];
		//}
	},
	
	//listVenue: function( args, venue ) {
	//	return [
	//		'<div class="ZventsVenueName">',
	//			args.map ? Z.render( 'venueIcon', args, venue ).wrap( 'ZventsEventListDot' ) : '',
	//			Z.render( 'listVenue', { render:args.render, description:args.description }, event.venue ),
	//		'</div>'
	//	].join();
	//},
	
	listEvent: function( args, event ) {
		var img = args.images ? Z.linkImage({ event:event }) : '';
		img = img && img.wrap( 'ZventsEventListImage' );
		return [
			'<div class="ZventsEvent', event.sponsored ? ' ZventsEventSponsored' : '', '">',
				img,
				'<div class="ZventsEventName">',
					Z.linkEventName( event ).wrap( 'ZventsEventListName' ),
				'</div>',
				( args.description ?
					'<div class="ZventsEventDescription">' +
						Z.miniFormat( event.description.truncate(args.description) ) +
					'</div>' :
					null ),
				'<div class="ZventsWhenWhere">',
					( /*args.search*/true ? Date(event.startTime).formatLongDate(false,true) + ' - ' : '' ),
					Date.formatTimeRange( event.startTime, event.endTime ),
				'</div>',
				'<div class="ZventsEventClear">',
				'</div>',
			'</div>'
		].join();
	},
	
	allTable: function( args, day ) {
		return Z.render( 'agenda', Object.combine( args, { date: true } ), day );
	},
	
	pager: function( args ) {
		var params = Z.copyParams( args );
		var offset = +params.offset || 0;
		var limit = +params.limit || 10;
		if( ! offset  &&  ! args.more ) return;
		if( params.search ) delete params.date;
		return [
			'<div class="ZventsPager">',
				prev(),
				here(),
				next(),
			'</div>'
		].join();
		
		function prev() {
			if( ! offset ) return '';
			var p = Z.copyParams( params );
			p.offset = offset - limit;
			if( ! p.offset ) delete p.offset;
			return link( p, '< Prev' );
		}
		
		function here() {
			return [
				'<span class="ZventsPagerHere">',
					' Page ', offset / limit + 1, ' ',
				'</span>'
			].join();
		}
		
		function next() {
			if( ! args.more ) return '';
			var p = Z.copyParams( params );
			p.offset = offset + limit;
			return link( p, 'Next >' );
		}
		
		function link( p, text ) {
			return [
				'<a href="', Z.link(p), '" class="ZventsPagerLink">', text, '</a>'
			].join();
		}
	},
	
	venueIcon: function( args, venue ) {
		if( ! venue ) return '';
		if( venue.venue ) {
			var event = venue, sponsored = event.sponsored;
			venue = event.venue;
		}
		return Z.imgTag({
			id: 'ZventsMapDot'+ venue.id,
			'class': "ZventsMapDot",
			src: Z.Map.venueIcon( venue, { sponsored:sponsored } ),
			border: "0",
			onclick: "Zvents.Map.clickVenueDot(" + venue.id + ")"
		});
	},
	
	venueAddress: function( args, venue ) {
		return [
			venue.address || '', ', ', venue.city || '', ' ', venue.state || '', ' ', venue.zip || ''
		].join();
	},
	
	siteLink: function( args, url ) {
		return ! url ? '' : [
			'<a class="ZventsSiteLink" href="', Z.linkToSite(url), '">',
				url.replace( /^http:\/\//, '' ).replace( /\/$/, '' ).htmlEscape(),
			'</a>'
		].join();
	},
	
	_:_
};


Z.Calendar = function( args ) {
	this.args = args;
	this.inline = args.inline; // TODO - generalize this arg copying
	this.linkMonths = {};
	this.linkDays = args.linkDays || {};
	this.selectedDay = this.args.date || Date.today();
	this.setMonth();
};

Z.Calendar.prototype = {
	setMonth: function() {
		var date = this.args.date;
		// This is a nasty bit of code - need to refactor
		delete this.shown;
		if( Z.event ) {
			this.firstDay = Z.event.events[Z.event.args.event];
		}
		else if( this.args.sparseRange ) {
			this.shown = Z.events && Z.events.events && Z.events.events.days;
			this.firstDay = this.shown && this.shown[0];
		}
		else {
			var events = Z.events && Z.events.events;
			if( events && events.firstDate ) {
				this.shown = { first: events.firstDate, last: events.lastDate };
			}
			this.firstDay = this.shown && this.shown.first;
		}
		this.shown = this.shown || { first: date, last: date };
		this.firstDay = this.firstDay || { date: date };
		var first = this.firstDay && this.firstDay.date || this.args.date;
		this.firstWeek = this.args.firstWeek || (
			this.args.scope == 'day' ? first :
			this.args.scope == 'week' ? Date(first).beginWeek() :
			Date(first).firstWeekOfMonth()
		);
		this.monthFirst = Date( Date(this.firstWeek).addDaysAtMidnight(6) ).beginMonth();
	},
	
	render: function( args ) {
		var rowBorder = '<tr class="ZventsCalendarDayBorderRow"><td class="ZventsCalendarDayBorderRowCol" colspan="13"></td></tr>';
		var colBorder = '<td class="ZventsCalendarDayBorderCol"></td>';
		var self = this;
		var displayMonth;
		Object.extend( self.args, args );
		self.setMonth();
		var title = self.renderTitle();
		return {
			title: title,
			navBar: Z.render( 'navBar', self.args, {
				title: title,
				viewMenu: true,
				spinner: self.args.widgets.calendar.spinner !== false
			}),
			content: renderCalendar()
		};
		
		function renderCalendar() {
			var render = {
				all: renderCalendarAll,
				month: renderCalendarWeeks,
				week: renderCalendarWeek,
				day: renderCalendarDay,
				event: renderCalendarEvent,
				venue: renderCalendarVenue
			}[Z.opt.scope];
			return [
				'<div class="ZventsCalendar">',
					render ? render() : '',
				'</div>'
			].join();
		}
		
		function renderCalendarHeader() {
			var length = Z.opt.widgets.calendar.weekdayLabel || 3;
			var cells = [];
			for( var i = 0;  i < 7;  i++ ) {
				var name = Date.dayNames[i].substring( 0, length );
				cells[cells.length] = cell( name );
			}
			return [
				'<tr class="ZventsCalendarHeaderRow">',
					cells.join( colBorder ),
				'</tr>'
			].join();
			
			function cell( text ) {
				return [
					'<th class="ZventsCalendarHeaderCell">',
						text,
					'</th>'
				].join();
			}
		}
		
		function renderCalendarEvent() {
			var event = Z.events.events[0];
			if( ! event ) return '';
			return [
				'<div class="ZventsCalendarEvent">',
					Z.render( 'event', self.args, event ),
				'</div>'
			].join();
		}
		
		function renderCalendarVenue() {
			var venue = Z.events.venues[0];
			if( ! venue ) return '';
			return [
				'<div class="ZventsCalendarVenue">',
					Z.render( 'venuePage', self.args, venue ),
				'</div>'
			].join();
		}
		
		function renderCalendarDay() {
			return Z.render( 'dayTable', self.args, self.firstDay );
		}
		
		function renderCalendarWeeks() {
			return [
				'<div class="ZventsCalendarWeeks">',
					'<table class="ZventsCalendarWeekTable" cellspacing="0">',
						'<tbody class="ZventsCalendarWeekTableBody">',
							renderCalendarHeader(),
							renderWeeks( self.args.weeks || 6 ),
						'</tbody>',
					'</table>',
				'</div>'
			].join();
		}
		
		function renderCalendarWeek() {
			return Z.render( 'weekTable', self.args, self.firstWeek );
		}
		
		function renderCalendarAll() {
			return Z.render( 'allTable', self.args, self.firstWeek );
		}
		
		function renderWeeks( weeks ) {
			self.firstShown = false;
			var html = [];
			for( var week = self.firstWeek;  weeks-- > 0;  week = Date(week).nextWeek() ) {
				html.push( renderWeek( week, weeks == 0 ) );
			}
			return html.join();					
		}
		
		function renderWeek( day, last ) {
			var days = [];
			for( var i = 0;  i < 7;  i++, day = Date(day).nextDay() ) {
				days.push( renderDay( day ) );
			}
			return [
				rowBorder,
				'<tr class="ZventsCalendarWeekTableRow" valign="top">',
					days.join( colBorder ),
				'</tr>'
			].join();
		}
		
		function renderDay( time ) {
			var date = Date( time );
			var dayOfMonth = date.getDate();
			var dateClass = Z.timeClass( "ZventsCalendarDay", time );
			var weekdayClass = date.isWeekend() ? "ZventsCalendarDayWeekend" : "ZventsCalendarDayWeekday";
			var todayClass = date.isToday() ? "ZventsCalendarDayToday" : "ZventsCalendarDayNotToday";
			var selected = time == self.selectedDay;
			return ( self.args.days == 1 ? renderOne : self.inline ? renderInline : renderNumber )().join();
			
			// TODO: copy & paste!
			function renderOne() {
				var title = ' title="' + Date(time).formatLongDate( true ) + '"';  // copy and paste HACK
				return [
					'<td class="ZventsCalendarDayCell ZventsCalendarDayInline ', dateClass, '">',
						'<div class="ZventsCalendarDayBody ', self.monthClass(time), '">',
							'<div class="ZventsCalendarDayContent ZventsCalendarDayEvents">',
								self.renderEvents(time),
							'</div>',
						'</div>',
					'</td>'
				];
			}
			
			function renderInline() {
				var date = Date( time );
				var title = ' title="' + date.formatLongDate( true ) + '"';  // copy and paste HACK
				var month = date.M();
				var format = ( month == displayMonth ? '{D}' : '{Jan} {D}' );
				displayMonth = month;
				return [
					'<td class="ZventsCalendarDayCell ZventsCalendarDayInline ', dateClass, '">',
						'<div class="ZventsCalendarDayBody ', self.monthClass(time), '">',
							'<a class="ZventsCalendarDayLink" href="', self.args.linker && self.args.linker(time) || Z.linkToDate(time), '"', title, '>',
								'<span class="ZventsCalendarInlineDate ', Z.timeClass("ZventsCalendarInlineDate",time), '">',
									'<span class="', weekdayClass, '">',
										'<span class="', todayClass, '">',
												date.format( format ),
										'</span>',
									'</span>',
								'</span>',
							'</a>',
							'<div class="ZventsCalendarDayContent ZventsCalendarDayEvents">',
								self.renderEvents(time),
							'</div>',
						'</div>',
					'</td>'
				];
			}
		}
	},
	
	renderTitle: function() {
		var self = this;
		var time = self.args.date;
		var date = Date( time );
		var day = date.getDay();
		//var mdy = [ date.getFullYear(), Z.Date.longMonth(date.getMonth()), date.getDate() ].join(' ');
		var weekdayClass = date.isWeekend() ? "ZventsCalendarDayWeekend" : "ZventsCalendarDayWeekday";
		var scope = self.args.scope || 'month';
		
		var formats = {
			all: function() {
				try {
					var days = Z.events.events.days;
					var last = days[days.length-1].date;
				}
				catch( e ) {
					return;
				}
				return Date.formatLongDateRange( time, last  );
			},
			month: function() {
				return date.format( '{January} {Y}' );
			},
			week: function() {
				var begin = date.beginWeek();
				return Date.formatLongDateRange( begin, Date(begin).addDaysAtMidnight(6)  );
			}
		};
		var format = formats[scope] || function() {
			return date.format( '{Sunday}, {January} {D}, {Y}' );
		};
		return format();
	},
	
	_:_
};


Z.EventList = function( args ) {
	this.args = Object.combine(
		{ offset:0, limit:10, render:{} },
		args
	);
	this.venues = [].index();
	this.events = [].index();
	this.events.days = [].index();
};

Z.EventList.prototype = {
	anyVenues: function() {
		var events = this.events;
		for( var i = 0, n = events.length;  i < n;  i++ )
			if( events[i].venue )
				return true;
		return false;
	},
	
	load: function( args ) {
		var self = this;
		args = Object.combine( this.args, args );
		args.days = args.days || 1;
		if( args.date  &&  ! args.endtime )
			args.endtime = Date(args.date).addDaysAtMidnight( args.days ) - Date.oneSecond;
		//var first = Z.Date.firstWeekOfMonth( args.date );
		var first = args.date;
		
		Z.Json.load({
			name: 'Zvents_EventList_load',
			args: args,
			yields: function( json ) {
				Z.json.events = json;
				try {
					function append( to, from, sponsored ) {
						if( ! from ) return;
						for( var i = 0, len = from.length;  i < len;  i++ ) {
							var event = from[i];
							to[to.length] = event;
							event.index = i + 1;
							if( sponsored ) event.sponsored = true;
						}
					}
					
					//alert( json.toSource() );
					var events = self.events;
					var venues = self.venues;
					var rsp = json.rsp;
					switch( rsp.status ) {
					case 'error':
						self.error = events.error = venues.error = rsp.msg;
						break;
					case 'ok':
						var content = rsp.content;
						self.more = content.next_page;
						
						append( events, content.event_ads, true );
						append( events, content.events );
						
						venues = self.venues = ( content.venues || [] ).index();
						events.repeats = 0;
						events.total = content.event_count;
						venues.total = content.venue_count;
						
						venues.each( function( venue ) {
							venue.events = [];
							venues.by[venue.id] = venue;
						});
						
						events.each( function( event ) {
							events.by[event.id] = event;
							var venue = event.venue = venues.by[event.vid];
							if( venue ) {
								if( venue.parent_id ) venue = event.venue = venues.by[venue.parent_id];
								if( venue ) {
									venue.events[venue.events.length] = event;
									if( event.sponsored ) venue.sponsored = true;
								}
							}
							event.date = Date(event.startTime).midnight();
							events.repeats += ( event.repeats = event.sc );
						});
						
						var index = 0;
						venues.each( function( venue ) {
							if( ! venue.parent_id ) {
								var solo = venue;
								venue.index = index++;
							}
						});
						//if( index == 1 ) venues.solo = solo;
						var date, current, days = events.days, iPin = 1;
						events.each( function( event ) {
							var venue = event.venue;
							venue = venue.parent_id || venue;
							venue.iPin = venue.iPin || iPin++;
							if( event.date !== date ) {
								date = event.date;
								if( ! events.firstDate ) events.firstDate = date;
								events.lastDate = date;
								days.push( current = { id: date, date: date, events: [] } );
							}
							current && current.events.push( event );
						});
						events = self.events = Object.sort( events, function( event ) { return event.venue.iPin; }, true );
						break;
					}
				}
				catch( e ) {
				}
				finally {
				}
				
				args.yields && args.yields( self );
				Z.opt.yields && Z.opt.yields( self );
			}
		});
	},
	
	render: function( args ) {
		args = Object.combine( this.args, args, { more: this.more } );
		return Z.render( 'venues', args, this.venues );
	}
};


Z.Map = function( args ) {
	var self = this;
	this.args = args = Object.combine( { controls:{}, render:{} }, args );
	this.nearby = Z.opt.widgets.nearby;
};

Z.Map.prototype = {
	connectList: function( items, getID ) {
		var self = this;
		items.each( function( item ) {
			var venue = item.venue || item;
			var id = getID( venue );
			self.connectItem( venue, id, $('#'+id) );
		});
	},
	
	connectItem: function( venue, id, $item ) {
		var self = this;
		venue.$item = $item;
		venue.icon = $('#'+id).e;
		$item.on( 'hover',
			function( e, hover ) {
				self[ hover ? 'select' : 'deselect' ]( venue );
			}
		);
	},
	
	select: function( venue, mouse ) {
		//this.hiliteVenue( venue );
		this.popOpen( venue, mouse );
	},

	deselect: function( venue ) {
		//this.unhiliteVenue( venue );
		this.popup && this.popup.close();
	},

	pointForLatLng: function( latlng ) {
		var g = this.g, map = this.map;
		var latlng0 = map.fromContainerPixelToLatLng( new g.GPoint(0,0), true );
		var divpixel0 = map.fromLatLngToDivPixel( latlng0 );
		var divpixel = map.fromLatLngToDivPixel( latlng );
		var point = new g.GPoint( divpixel.x - divpixel0.x, divpixel.y - divpixel0.y );
		var box = $( map.getContainer() ).box();
		return { x: point.x /* - box.x */, y: point.y /* - box.y */ };
	}
};


Z.Map.venueIcon = function( venue, use ) {
	use = use || {};
	//return Z.imgUrl( 'map/' + icon() );
	var url = icon();
	//Z.log( url );
	return Z.imgUrl( 'map/' + url );
	
	function icon() {
		return use.shadow ? 'pin_shadow.png' : [
			use.pin ? 'pin_' : 'dot_',
			venue.solo ? 'solo' : venue.iPin,
			use.sponsored ? '_sponsored' : '',
			use.select ? '_select' : '',
			use.gif ? '.gif' : '.png'
		].join();
	}
};

Z.Map.clickVenueDot = function( id ) {
	var venue = Z.events.venues.by[id];
	this.g.GEvent.trigger( venue.marker, 'click' );
};

// End Zvents calendar objects


// Zvents calendar widgets


// Utility functions - TODO - where to put these


Z.url = function( base, params, delim ) {
	base = base.split('#')[0];  // TODO
	var a = [];
	for( var p in params ) {
		var v = params[p];
		if( v != null ) a[a.length] = [ p, v ].join('=');
	}
	return a.length == 0 ? base : [ base, a.sort().join('&') ].join( delim || '?' );
};

Z.linkToDate = function( time ) {
	return Z.link({ day: Date(time).YMD() });
};

Z.linkToEvent = function( event ) {
	var id = new Number( event.id );
	id.name = event.name;
	return Z.link({ event:id });
};

Z.linkToEmailEvent = function( event ) {
	return Z.linkToEmail( event.name.htmlEscape(), Z.linkToEvent(event) );
};

Z.linkToEmail = function( subject, url ) {
	return [ 'mailto:?subject=', subject, '&body=', url ].join();
};

Z.link = function( params ) {
	params = params || {};
	var p = params;
	if( ! p.url ) {
		var base = p.base || $.window.location.href;
		delete p.base;
		p = Object.copy( Z.hostParams );
		for( var name in params ) if( params[name] != null ) p[ Z.paramName(name) ] = params[name];
		if( typeof p.date == 'number' ) p.date = Date(p.date).YMD();
		p.url = Z.linkParams( base, p );
	}
	return Z.opt.url && Z.opt.url( p ) || p.url;
};

Z.linkWith = function( params ) {
	return Z.linkParams( $.window.location.href, Zvents.Object.combine( Zvents.queryParams, params ) );
};

Z.linkParams = function( base, params ) {
	return Z.url( base.replace( /#[^#]*$|$/, '' ), params, '#' );
};

// TODO: combine these?
Z.linkEventName = function( event, max ) {
	if( ! event || ! event.name ) return '';
	return [
		'<a class="ZventsEventNameLink" href="', Z.linkToEvent(event), '">',
			event.name.truncate( max, true ),
		'</a>'
	].join();
};

Z.linkVenueName = function( venue ) {
	if( ! venue || ! venue.name ) return '';
	var name = venue.name.htmlEscape();
	var url = Z.linkToVenue( venue );
	return url ?
		[ '<a class="ZventsVenueNameLink" href="', url, '">', name, '</a>' ].join() :
		name;
};

Z.linkImage = function( a ) {
	var thing = a.event || a.venue;
	try {
		var image = thing.images[0];
		if( typeof image == 'string' )
			image = { url:image };  // temp compatibility hack
	}
	catch( e ) {}
	var url = a.url ? a.url : a.event ? Z.linkToEvent(thing) : Z.linkToVenue(thing);
	return ! image ? '' : [
		'<a class="ZventsImageLink" href="', url, '">',
			'<img class="ZventsImage" alt="Image" border="0" ',
				'src="', Z.imgThumb( image.url, a.large ), '" ',
				//'width="', image.width, '" height="', image.height, '" ',
			'/>',
		'</a>'
	].join();
};

Z.linkToVenue = function( venue ) {
	var id = new Number( venue.id );
	id.name = venue.name;
	return Z.link({ venue:id });
};

Z.linkFilterNameCount = function( filter, param, selected ) {
	return [
		Z.linkFilterName( filter, param, selected ),
		filter.length ? '<span class="ZventsFilterCount"> (' + filter.length + ')</span>' : ''
	].join();
};

Z.linkFilterName = function( filter, param, selected ) {
	if( ! filter || ! filter.name ) return '';
	var name = filter.name.htmlEscape();
	return(
		selected ? [
			'<span class="ZventsFilterSelected">',
				name,
			'</span>'
		] : [
			'<a href="', Z.linkToFilter(filter,param), '">',
				name,
			'</a>'
		]
	).join();
};

Z.linkToFilter = function( filter, param ) {
	var params = { offset:null };
	params[param] = filter.id;
	return Z.link( Object.combine( Z.urlParams, params ) );
};

Z.linkToSite = function( url ) {
	return Z.link({ url:url });
};

function argtime( time ) {
	return Date(time).time() / 1000 + '';
}

// Temp hack to add _thumb
Z.imgThumb = function( url, large ) {
	var suffix = large ? /*'_primary'*/'' : '_thumb';
	return url.replace( /_thumb\./, '.' ).replace( /\.([^.]+)$/, suffix + '.$1' );
};

Z.imgUrl = function ( file ) {
	return [ Z.imageBaseUrl, 'images/', file ].join();
};

Z.imgTag = function( attrs ) {
	var src = attrs.src;
	if( Z.useIePng(src) ) {
		attrs.src = Z.imgUrl('spacer.png');
		attrs.style = [ ( attrs.style || '' ), 'filter:', Z.ieImgFilter(src) ].join();
	}
	return 'img'.tag( attrs );
};

Z.setImg = function( img, src ) {
	if( Z.useIePng(src) ) img.style.filter = Z.ieImgFilter( src );
	else img.src = src;
};

Z.ieImgFilter = function( src ) {
	return [
		"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='", src, "', sizingMethod=scale);"
	].join();
};

Z.useIePng = function( src ) {
	return $.browser.iePngHack  &&  src.search(/\.png$/i) > 0;
};

Z.venueIsSponsored = function( venue ) {
	var events = venue.events;
	if( ! events ) return false;
	for( var i = 0, len = events.length;  i < len;  i++ )
		if( events[i].sponsored ) return true;
	return false;
};

Z.paramName = function( name ) {
	return Z.paramName.names[name] || name;
};

Z.paramName.names = {};


var $netcal;

function setContent( html, home ) {
	if( home ) $('#ZventsCalendarLoaderWrapper').e.scrollTop = 0;
	$('#ZventsCalendarFrame').html( html );
}

function viewCalendar( scope, a ) {
	Z.opt.scope = scope;
	Z.calendar = new Z.Calendar( Object.combine( Z.opt, { scope:scope, date:a.date, firstWeek:a.first, days:a.days, weeks:a.days && a.days/7, images: Z.opt.images !== false && scope != 'month'  } ) );
	Z.events = new Z.EventList( Object.combine( Z.opt, { scope:scope, date:a.first, days:a.days, limit:10, event:a.event, venue:a.venue, description:a.description } ) );
	Z.events.load({ yields: function( events ) {
		if( ! viewCalendar.framed ) {
			viewCalendar.framed  = true;
			( $netcal = $netcal  || $( '#' + Z.opt.netcal ) )
				.html( frame() )
				.on( 'click', clickCalendar );
		}
		
		var rendered = Z.calendar.rendered = Z.calendar.render();
		
		$('#ZventsNavBar').html( rendered.navBar );
		setContent( rendered.content, true );
		
		function frame() {
			return [
				'<div id="ZventsCalendarFrame" class="ZventsWidget ZventsCalendarFrame">',
				'</div>'
			].join();
		}
	} });
}

function clickCalendar( e ) {
	//var link = $(e.target).parent( 'a', $netcal.e );
	//if( ! link ) return;
	//var ls = link.href.split( '#' );
	//if( ls[0] == window.location.href.split('#')[0] )
	//	$.history.set( ls[1] );
}

function loadCalendar( hash ) {
	var p = hash.split( '=' );
	var view = views[ p[0] ];
	if( view ) {
		Z.opt.scope = p[0];
		view( p[1] );
	}
}

var views = {
	all: function( date ) {
		date = Date( date );
		viewCalendar( 'all', { date: date.time(), first: date.time(), days: 366, map: Z.opt.map } );
	}
};

// End of Zvents calendar widgets

// Search.js

var ellipsis = '...';  // '&#8230;';

var iphone = $.browser.iphone;
if( iphone ) {
	document.write( [
		'<meta name="viewport" content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=false" />'
	].join() );
}


var params = {}, narrow;

(function() {
	var p = location.hash.replace( /^#/, '' ).split('&');
	for( var i = 0, n = p.length;  i < n;  ++i ) {
		var pv = p[i].split('=');
		params[ pv[0] ] = pv[1];
	}
})();

var linkParams = [
	'medium=' + ( params.medium || 'maps' ),
	'source=' + ( params.source || 'google' ),
	'term=' + ( params.widget || 'widget' ),
	'sid=' + Z.session
].join('&')


//document.write( $.viewport().cx + '<br />' );


function dayPickers( nDays ) {
	nDays = nDays || 5;
	var buttons = [];
	
	var todayClass = ' ZventsDayButtonToday';
	var today = new Date( (new Date).setHours( 0, 0, 0, 0 ) );
	for( var i = 1;  i <= nDays;  ++i ) {
		var when = today.format( '{M}/{D}/{Y}' );
		var day = today.getDay();
		var weekend = ( day == 0  ||  day == 6 );
		var weekendClass = weekend ? ' ZventsDayButtonWeekend' : '';
		var when = ( i == 1 ? 'Today' : i == 2 ? 'Tomorrow' : today.Sunday() );
		button( today.getDate(), today.Sun(), when, today.format( '{Sunday}, {January} {D}, {Y}' ) );
		todayClass = '';
		today.setDate( today.getDate() + 1 );
	}
	

	return buttons.join();

	function button( date, day, when, title, selectedClass, id ) {
		buttons.push( [
			'<td>',
				'<a href="#" ',
					'onclick="return dateSearch(this,\'', when, '\');" ',
					id ? 'id="' + id + '" ' : '',
					'class="ZventsDayButton', todayClass, weekendClass, selectedClass || '', '" ',
					'title="View events for ', title, '" ',
				'>',
				'<span class="ZventsDayButtonDate">',
					'&nbsp;', date, '&nbsp;',
				'</span>',
				'<span class="ZventsDayButtonDay">',
					day,
				'</span>',
				'</a>',
			'</td>'
		].join() );
	}
}

/*
document.write( [
	'<div>',
		'document.documentElement.clientWidth: ', document.documentElement && document.documentElement.clientWidth,
	'</div>',
	'<div>',
		'document.body.clientWidth: ', document.body && document.body.clientWidth,
	'</div>',
	'<div>',
		'window.innerWidth: ', window.innerWidth,
	'</div>',
].join() );
*/
	
function writeBody() {
	function field( which ) {
		// ZventsWhen, ZventsWhat, ZventsWhere, ZventsWhenLabel, ZventsWhatLabel, ZventsWhereLabel
		var id = 'Zvents' + which;
		return [
			'<td class="ZventsSearchTableCell Zvents', which, 'TableCell">',
				'<div class="overlabel-wrapper">',
					'<label id="Zvents', which, 'Label" for="', id, '" class="ZventsSearchLabel Zvents', which, 'Label overlabel">',
						which,
					'</label>',
					'<input id="', id, '" class="ZventsSearchInput Zvents', which, '" name="', which.toLowerCase(), '" value="" type="text" />',
				'</div>',
			'</td>'
		].join();
	}
	
	narrow = params.narrow  ? params.narrow  == 'true' : iphone || $.viewport().cx <= 400;
	var map = ! iphone;
	
	var searchForm = [
		'<form name="ZventsSearchForm" class="ZventsSearchForm">',
			
			'<table class="ZventsSearchButtonTable" cellspacing="0" style="margin-bottom: 2px;">',
				'<tbody>',
					'<tr>',
						'<td>',
							'<button disabled type="submit" id="ZventsSearch" class="ZventsButton ZventsSearch" name="search" value="true"> Search</button>',
						'</td>',
						
						'<td>',
							'<table cellspacing="0">',
								'<tbody>',
									'<tr>',
										'<td style="white-space:pre; line-height:90%; padding-right:4px;">',
											'<input class="ZventsRadio" name="ZventsModeRadio" id="ZventsRadioMovies" value="movies" checked="checked" onclick="radioSearch();" type="radio" />',
											'<label for="ZventsRadioMovies">',
												'Movies',
											'</label>',
										'</td>',
									'</tr>',
									'<tr>',
										'<td style="white-space:pre; line-height:90%; padding-right:4px;">',
											'<input class="ZventsRadio" name="ZventsModeRadio" id="ZventsRadioEvents" value="events" onclick="radioSearch();" type="radio" />',
											'<label for="ZventsRadioEvents">',
												'Events',
											'</label>',
										'</td>',
									'</tr>',
								'</tbody>',
							'</table>',
						'</td>',
						
						//'<td>',
						//	'<div style="white-space:pre;">',
						//	//'<div style="white-space:pre; margin-bottom:-3px;">',
						//		'<input class="ZventsRadio" name="ZventsModeRadio" id="ZventsRadioMovies" value="movies" onclick="submitSearch();" type="radio" checked="checked" />',
						//		'<label for="ZventsRadioMovies">',
						//			'Movies',
						//		'</label>',
						//	//'</div>',
						//	//'<div style="white-space:pre;">',
						//		'<input class="ZventsRadio" name="ZventsModeRadio" id="ZventsRadioEvents" value="events" onclick="submitSearch();" type="radio" />',
						//		'<label for="ZventsRadioEvents">',
						//			'Events',
						//		'</label>',
						//	'</div>',
						//'</td>',
						
						'<td style="text-align: right; width: 99%;">',
							'<a href="http://www.zvents.com/events/new?', linkParams, '" target="_blank" style="margin-right:6px; white-space: pre;" alt="Add a new event at Zvents.com" title="Add a new event at Zvents.com">Add an event</a>',
						'</td>',
						'<td>',
							'<div class="ZventsSpinner">',
								'<a class="ZventsSpinnerLink" href="http://www.zvents.com/?', linkParams, '" target="_blank">',
									'<img id="ZventsSpinnerLogo" src="', Z.imgUrl('zbutton.gif'), '" style="width: 20px; height: 20px; display: none;" alt="Visit Zvents.com" title="Visit Zvents.com" border="0">',
									'<img id="ZventsSpinnerActive" src="', Z.imgUrl('spinner16.gif'), '" style="width: 16px; height: 16px; margin: 2px;" alt="Searching', ellipsis, '" title="Searching', ellipsis, '" border="0">',
								'</a>',
							'</div>',
						'</td>',
					'</tr>',
				'</tbody>',
			'</table>',
			
			'<table cellspacing="0" style="width:100%;">',
				'<tbody>',
					'<tr>',
						dayPickers(),
					'</tr>',
				'</tbody>',
			'</table>',
			
			'<table class="ZventsSearchTable" cellspacing="0" xwidth="100%">',
				'<tbody>',
					'<tr class="ZventsSearchTableRow">',
						field( 'When' ),
						field( 'What' ),
						field( 'Where' ),
					'</tr>',
				'</tbody>',
			'</table>',
			
		'</form>'
	].join();
	
	var calendarLoader = [
		'<div id="ZventsCalendarLoaderWrapper" style="overflow: auto;">',
			'<div id="ZventsCalendarLoader" style="">',
				'<div style="margin: 6px 0;">',
					'<span style="font-size:18px;">',
						'Searching', ellipsis,
					'</span>',
				'</div>',
			'</div>',
		'</div>'
	].join();
	
	document.write( ( mapplet ? [
		'<div class="ZventsSearchFrame">',
			searchForm,
			calendarLoader,
		'</div>'
	] : narrow ? [
		'<table class="ZventsNarrow ZventsSearchFrame" style="width:100%; height:100%;" cellspacing="0">',
			'<tbody>',
				'<tr valign="top">',
					'<td style="width:99%;">',
						'<div style="xwidth:97%;">',
							'<div>',
								searchForm,
							'</div>',
							map ? '<div id="map" style="width:100%; height:200px;"></div>' : '',
							'<div style="margin:4px 0 0 0;">',
								calendarLoader,
							'</div>',
						'</div>',
					'</td>',
					'<td style="width:1%;">',
						' ',
					'</td>',
				'</tr>',
			'</tbody>',
		'</table>'
	] : [
		'<table class="ZventsSearchFrame" style="width:100%; height:100%;">',
			'<tbody>',
				'<tr valign="top">',
					'<td style="width:1%;">',
						'<div style="height:100%;">',
							searchForm,
							calendarLoader,
						'</div>',
					'</td>',
					'<td style="width:99%;">',
						'<div id="map" style="width:100%; height:100%;">',
						'</div>',
					'</td>',
				'</tr>',
			'</tbody>',
		'</table>'
	] ).join('') );
	
	$([ '#ZventsWhenLabel', '#ZventsWhatLabel', '#ZventsWhereLabel' ]).overlabel();
}

if( mapplet )
	writeBody();

function enable() {
	enable = function() {};
	
	$search = $('#ZventsSearch');
	$search.e.disabled = false;
	$search.on( 'click', submitSearch );
}

function radioSearch() {
	$('#ZventsWhatLabel').setlabelvalue( '' );
	submitSearch();
}
	
function submitSearch() {
	enable();
	$search.e.blur();
	if( mapplet )
		map.closeInfoWindow();
	else
		showDetail.now();
	throttle( null );
	search( true );
	return false;
}

function searchWhat( what ) {
	var $label = $('#ZventsWhatLabel');
	if( $label.getlabelvalue() != what ) {
		$label.setlabelvalue( what );
		return submitSearch();
	}
}

var dateSelected = {
	set: function( e ) {
		this.$e = $(e);
		this.$e.addClass( 'ZventsDayButtonSelected' );
	},
	
	clear: function() {
		var $e = this.$e || $('#ZventsDayButtonAnyDay');
		$e.removeClass( 'ZventsDayButtonSelected' );
		delete this.$e;
	}
};

function dateSearch( e, date ) {
	$('#ZventsWhenLabel').setlabelvalue( date || '' );
	dateSelected.clear();
	mapMoved();
	if( e ) dateSelected.set( e );
	return false;
}

var state = {};

function whenLoaded( yields ) {
	GAsync( map, 'isLoaded', function( loaded ) {
		if( loaded ) {
			whenLoaded = function( yields ) { yields(); };
			yields();
		}
	});
}

function throttle( yields ) {
	var last = throttle.last;
	throttle.last = (new Date).getTime();
	
	clearTimeout( throttle.timer );
	throttle.timer = null;
	
	if( ! yields ) return;
	
	function ready() {
		throttle.timer = throttle.last = null;
		yields();
	}
	
	if( last  &&   throttle.last - last < Date.seconds(2) )
		throttle.timer = setTimeout( ready, Date.seconds(2) );
	else
		yields();
}

// GAsync by Michael Geary
// http://mg.to/2007/06/06/a-fast-and-simple-async-api-for-google-mapplets
function GAsync( obj ) {
	
	function callback() {
		args[nArgs].apply( null, results );
	}
		
	function queue( iResult, name, next ) {
		
		function ready( value ) {
			results[iResult] = value;
			if( ! --nCalls )
				callback();
		}
		
		var a = [];
		if( next.join )
			a = a.concat(next), ++iArg;
		if( mapplet ) {
			a.push( ready );
			obj[ name+'Async' ].apply( obj, a );
		}
		else {
			results[iResult] = obj[name].apply( obj, a );
		}
	}
	
	var mapplet = ! window.GBrowserIsCompatible;
	var args = arguments, nArgs = args.length - 1;
	var results = [], nCalls = 0;
	
	for( var iArg = 1;  iArg < nArgs;  ++iArg ) {
		var name = args[iArg];
		if( typeof name == 'object' )
			obj = name;
		else
			queue( nCalls++, name, args[iArg+1] );
	}
	
	if( ! mapplet )
		callback();
}


function mapMoved() {
	whenLoaded( function() {
		GAsync( map, 'getSize', 'getBounds', 'getCenter', function( size, bounds, center ) {
			enable();
			if( ! infoWindowMoving ) {
				state = mapState({ size:size, bounds:bounds, center:center });
				//if( myInfoWindow )
				//	map.closeInfoWindow();
				search( false );
			}
		});
	});
}


function mapState( state ) {
	var pix = Math.min( state.size.width, state.size.height ) - 40;
	if( pix <= 40 ) return;
	var factor = pix / state.size.height;
	
	var ne = state.bounds.getNorthEast();
	var sw = state.bounds.getSouthWest();
	var degrees = Math.abs( ne.lat() - sw.lat() ) * factor;
	var miles = degrees * Z.milesPerDegree;
	state.radius = miles / 2;
	
	return state;
}


var pinned = [];

function pinVenues() {
	var wasPinned = pinned;
	pinned = [];
	var venues = Z.events.venues;
	
	venues.each( function( venue ) {
		if( venue.latitude && venue.longitude ) pinVenue( venue );
	});
	
	wasPinned.each( unpinVenue );
	
	//connectList( venues, function( venue ) { return 'ZventsMapDot' + venue.id; } );
}

function pinVenue( venue ) {
	//console.debug( venue );
	pinned.push( venue );
	
	var icon = new GIcon();
	icon.image = Z.Map.venueIcon( venue, { pin:true, sponsored:Z.venueIsSponsored(venue) } );
	//icon.shadow = Z.Map.venueIcon( venue, { shadow:true } );
	icon.iconSize = new GSize( 20, 27 );
	//icon.shadowSize = new GSize( 37, 36 );
	icon.iconAnchor = new GPoint( 0, 27 );
	icon.infoWindowAnchor = new GPoint( 11, 0 );

	var latlng = new GLatLng( venue.latitude, venue.longitude );
	var marker = venue.marker = new GMarker( latlng, { icon:icon } );
	
	map.addOverlay( marker );
	
	if( mapplet ) {
		GEvent.addListener( marker, 'click', function() {
			//myInfoWindow = true;
			marker.openInfoWindowHtml( mapInfo({ popup:{ events:true } }, venue ) );
		});
	}
	else {
		GEvent.addListener( marker, 'mouseover', function() {
			showDetail.hover( venue );
		});
		GEvent.addListener( marker, 'mouseout', function() {
			showDetail.hover( null );
		});
	}
}

function showLinkLine( venue ) {
	var my = showLinkLine;
	if( my.line ) {
		//window.console && console.debug( 'remove' );
		map.removeOverlay( my.line );
		delete my.line;
	}
	
	if( venue ) {
		//window.console && console.debug( 'add' );
		if( narrow ) {
			var y = map.getSize().height + 8;
			var pt1 = new GPoint( 0, y ), pt2 = new GPoint( 24, y );
		}
		else {
			var y = $('#ZventsCalendarFrame').box().y;
			var pt1 = new GPoint( -10, y + 1 ), pt2 = new GPoint( -10, y + 13 );
		}
		my.line = new GPolygon( [
			map.fromContainerPixelToLatLng( pt1 ),
			new GLatLng( venue.latitude, venue.longitude ),
			map.fromContainerPixelToLatLng( pt2 )
		], '#4C61B6', 2, .44, '#4C61B6', .33 );
		map.addOverlay( my.line );
	}
}

function showDetail( venue ) {
	if( venue ) {
		var r = Object.combine( render, mode == 'movies' && renderMovies );  // HACK
		var html = venue.html = venue.html ||
			Z.render( 'venue', { render:r, details:true, /*description:true,*/ images:true, map:true }, venue );
	}
	else {
		var rendered = Z.calendar.rendered || '';
		html = rendered && rendered.content;
	}
	
	setContent( html, venue );
	showLinkLine( venue );
}

function showDetailClick( venue ) {
	showDetail( venue );
}

function unpinVenue( venue ) {
	if( venue.marker ) {
		map.removeOverlay( venue.marker );
		delete venue.marker;
	}
}


Z.Map.clickVenueDot = function( id ) {
	var venue = Z.events.venues.by[id];
	GEvent.trigger( venue.marker, 'click' );
};


function Zurl( item ) {
	return item.zurl ? 'http://www.zvents.com' + item.zurl + '?' + linkParams : '';
}

var render = {
	
	agenda: function( args, day ) {
		var venues = Z.events.venues;
		setTimeout( pinVenues, 1 );
		if( mapplet )
			_IG_AdjustIFrameHeight();
		return Z.render( 'venues', args, venues );
	},
	
	event: function( args, event ) {
		return [
			'<div class="ZventsEventWrapper ', Z.timeClass("ZventsEventDate",event.date), '">',
				Z.render( 'listEvent', args, event ),
			'</div>'
		].join();
	},
	
	listFrame: function( args, html ) {
		return [
			'<div class="ZventsWidget ZventsEventFrame">',
				html,
			'</div>'
		].join();
	},
	
	listVenue: function( args, venue ) {
		var details = ! args.details ? '' : [
			'<div style="font-weight: bold; margin-top: 2px;">',
				venue.phone || '',
			'</div>',
			'<div style="margin-top: 2px;">',
				venue.address || '',
			'</div>',
			'<div>',
				venue.city || '',
				venue.city && venue.state ? ', ' : '',
				venue.state || '',
				venue.zip ? ' ' + venue.zip : '',
			'</div>'
		].join();
		
		return [
			'<div class="ZventsVenueInfo">',
				'<div class="ZventsVenueName">',
					args.map ? Z.render( 'venueIcon', args, venue ) : '',
					Z.linkVenueName( venue ),
				'</div>',
				'<div class="ZventsVenueDetails">',
					details,
				'</div>',
			'</div>'
		].join();
	},

	navBar: function() {
		return '';
	},
	
	resultsHeader: function( args, html ) {
		return ! html ? '' : [
			'<div class="ZventsResultsHeader">',
				html,
			'</div>'
		].join();
	},

	venue: function( args, venue ) {
		return [
			'<div class="ZventsVenueWrapper">',
				Z.render( 'listVenue', args, venue ),
				venue.events.map( function( event ) {
					return Z.render( 'event', args, event );
				}).join( '<div class="ZventsEventSeparator"></div>' ),
			'</div>'
		].join();
	},
	
	_:_
};

var renderMovies = {
	
	venue: function( args, venue ) {
		//console.debug( venue );
		var movies = [];
		// TODO: use fast sort
		venue.movies.sort( function( a, b ) {
			if (a.name < b.name) return -1
			else if (a.name > b.name) return 1
			else return 0;
		});
	
		venue.movies.each( function( movie ) {
			var showtimes = renderShowtimes( movie, venue );
			if( ! showtimes ) return;
			var title = movie.name.htmlEscape();
			var search = title.replace( /'/, "\\'" );
			movies.push( [
				'<div class="ZventsMapInfoVenueMovie">',
					'<a class="ZventsMapInfoVenueMovieTitle" href="', Zurl(movie), '" target="_blank" onclick="return searchWhat(\'', search, '\');">',
						title,
					'</a>',
					movie.ratings ? ' (' + movie.ratings + ')' : '',
					showtimes,
				'</div>'
			].join('') );
		});
		
		return [
			'<div class="ZventsMapInfoVenueMovies">',
				'<div class="ZventsMapInfoVenueMoviesHeader">',
					Z.render( 'listVenue', args, venue ),
				'</div>',
				'<div class="ZventsMapInfoVenueMoviesList">',
					movies.join(),
				'</div>',
			'</div>'
		].join('');
	},
	
	venues: function( args, venues ) {
		try {
			var content = Zvents.json.events.rsp.content;
			var movies = content.movies;
			movies.total = content.movie_count;
			if( Zvents.json.events.rsp.status == 'error' ) {
				movies.error = Zvents.json.events.rsp.msg;
			}
		}
		catch( e ) {
			movies = [];
		}
		
		Z.events.venues = venues = [].index();
		
		var index = 0;
		movies.index().each( function( movie ) {
			//movies.by[movie.id] = movie;
			movie.venues.index().each( function( venue ) {
				var v = venues.by[venue.id];
				if( v ) {
					venue = v;
				}
				else {
					venue.iPin = ++index;
					venues.push( venue );
				}
				if( venue ) {
					venue.movies = venue.movies || [];
					venue.movies.push( movie );
				}
			});
			movie.date = Date(movie.startTime).midnight();
			//movies.repeats += ( movie.repeats = movie.sc );
		});
		
		var html =
			movies.error ? [ Z.render( 'error', args, movies.error ) ] :
			renderSearch();
		return Z.render( 'listFrame', args, html.join() );
		
		function renderSearch() {
				//console.debug( venues );
			return [
				//Z.render( 'resultsHeader', args,
				//	movies.length ? '' : 'No events found' ),
				venues.map( function( venue ) {
					return Z.render( 'venue', args, venue );
				}).join()/*,
				Z.render( 'pager', args )*/
			];
		}
	},
	
	_:_
};

function renderShowtimes( movie, venue ) {
	var today = Date.today(), now = Date().getTime();
	venue = movie.venues.by[venue.id];
	var showtimes = venue.showtimes;
	if( ! showtimes.length ) return '';
	
	var tix = '', list = [ '' ];
	
	showtimes.each( function( showtime ) {
		//debugger;
		var time = showtime.starttime * 1000, date = Date(time);
		if( date.midnight() == today   &&  time < now - Date.minutes(30) ) return;
		var hmm = date.format( '{h}:{mm}{a}' );
		
		if( showtime.zurl )
			tix = ' <img src="' + Z.imgUrl('icons/ico_tix.gif') + '" style="width:24px; height:14px; vertical-align:middle;" />';
		
		var params = movie ?
			eventParams( showtime, movie.index, movie.id ) :
			'href="' + Zurl(showtime) + '"';
		
		list.push( ! showtime.zurl ? hmm : [
			'<a target="_blank" title="Buy tickets for this movie" ', params, '>', hmm, '</a>'
		].join() );
	});
	
	return list.length ? tix + list.join(' ') : '';
}

function eventParams( event, index, secondary_id ) {
	return event.sponsored ? 'href="' + event.url + '"' :
		aParams( 'events', Zurl(event), index, secondary_id );
}

function aParams( what, url, index, secondary_id ) {
	//var url = Z.url( url );
	////var rank, rid, seid, st;
	////if ( search_info ) {
	////	rank = index + ( search_info.offset || 0 );
	////	rid = search_info.rid || -1;
	////	seid = search_info.seid || -1;
	////	st = search_info.st;
	////}
	//var props = Zvents.joinProps( {
	//	//rank: rank,
	//	//rid: rid, 
	//	//seid: seid,
	//	//st: st,
	//	from: escape( ( location.pathname + location.search ).slice( 0, 200 ) ),
	//	to: escape( url ),
	//	secid: secondary_id
	//}, '=', '|' );
	//return [ 'href="', url, '" onclick="clicked(\'', props, '\')"' ].join('');
	return [ 'href="', url, '"' ].join();
}


function mapInfo( args, venue ) {
	if( ! venue ) return '';
	
	var events = '';
	var nEvents = args.popup && args.popup.events && venue.events && venue.events.length;
	if( nEvents ) {
		events = [];
		for( var i = 0;  i < nEvents;  i++ ) {
			var event = venue.events[i];
			events.push( [
				'<tr valign="top">',
					'<td style="border: 1px none rgb(222,222,222); border-top-style: solid;">',
						'<div style="margin: 3px 4px 2px 0;">',
							Z.linkEventName( event, 50 ),
						'</div>',
					'</td>',
					'<td style="border: 1px none rgb(222,222,222); border-top-style: solid;">',
						'<div style="margin: 3px 4px 3px 4px;">',
							Date(event.startTime).format( '{Y}-{MM}-{DD}<br />{h}:{mm} {am}' ),
						'</div>',
					'</td>',
				'</tr>'
			].join() );
		}
			
		events = [
			'<div style="margin-top: 6px; padding: 4px; overflow: auto; background-color:rgb(250,250,250); border: 1px solid rgb(210,210,210);">',
				'<table cellspacing="0" style="font-size: 10pt;">',
					'<tr style="font-weight: bold;">',
						'<td>',
							'<div style="margin: 0 4px 2px 0;">',
								'Event',
							'</div>',
						'</td>',
						'<td>',
							'<div style="margin: 0 4px 2px 4px;">',
								'Date',
							'</div>',
						'</td>',
					'</tr>',
					events.join(),
				'</table>',
			'</div>'
		].join();
	}
	
	return [
		'<div>',
			'<div style="font-weight: bold;">',
				Z.linkVenueName(venue),
			'</div>',
			'<div style="font-weight: bold; margin-top: 6px;">',
				venue.phone || '',
			'</div>',
			'<div style="margin-top: 6px;">',
				venue.address || '',
			'</div>',
			'<div>',
				venue.city || '',
				venue.city && venue.state ? ', ' : '',
				venue.state || '',
				venue.zip ? ' ' + venue.zip : '',
			'</div>',
			events,
		'</div>'
	].join();
}

var mode = 'movies';

function search( useWhere ) {
	mode = $('#ZventsRadioEvents').e.checked ? 'events' : 'movies';  // TODO: stupid?
	var movies = ( mode == 'movies' );
	var where = useWhere ? $where.e.value : '';
	//console.debug( movies );
	var json = Object.combine({
		search: 'true',
		what: $('#ZventsWhat').e.value,
		when: $('#ZventsWhen').e.value,
		where: where || state.center.lng() + ':BY:' + state.center.lat(),
		radius: where ? 5 : Math.min( state.radius, 3500 )
	},
	movies && {
		rid: 0,
		sc: 1,
		seid: 1,
		sort: 0,
		st: 'movie',
		uid: 0
	});
	//console.debug( json );
	Z({
		netcal: 'ZventsCalendarLoader',
		baseUrl: 'http://www.zvents.com/',
		//imageBaseUrl: 'http://mg.to/zmap/',
		lastOnly: true,
		//descriptions: true,
		images: true,
		map: ! iphone,
		views: {
			menu: [ 'all', 'week', 'day' ],
			initial: 'all'
		},
		json: json,
		yields: function( eventList ) {
			if( where ) {
				scaleMapTo( eventList );
			}
		},
		render: Object.combine( render, movies && renderMovies ),
		spin: function( spin ) {
			// TODO: a bit clumsy, refactor
			var on = $('#ZventsSpinnerActive').e.style, off = $('#ZventsSpinnerLogo').e.style;
			if( spin )
				on.display = 'inline', off.display = 'none';
			else
				off.display = 'inline', on.display = 'none';
		},
		url: function( p ) {
			// See related to_param function in /app/models/(event.rb|group.rb|movie.rb|venue.rb)
			if( p.event ) return to_param( 'events', p.event );
			if( p.venue ) return to_param( 'venues', p.venue );
			
			function to_param( type, id ) {
				return [
					'http://www.zvents.com/', type, '/show/', id, '-',
					id.name.replace( /[^a-z0-9]+/gi, '-' ).replace( /-+/g, '-' ),
					'?', linkParams, '" target="_blank'  // missing close quote is intentional
				].join();
			}
		},
		_:_
	});
}

var map, mapMoving, infoWindowOpen, infoWindowMoving/*, myInfoWindow*/;

if( mapplet ) {
	map = new GMap2;
	start();
}

var ignoreNextZoom;

function boundsCenter( bounds ) {
	var ne = bounds.getNorthEast();
	var sw = bounds.getSouthWest();
	return new GLatLng(
		( ne.lat() + sw.lat() ) / 2,
		( ne.lng() + sw.lng() ) / 2
	);
}

function scaleMapTo( eventList ) {
	try {
		var bounds = new GLatLngBounds;
		var set;
		eventList.venues.each( function( venue ) {
			if( venue.latitude && venue.longitude ) {
				bounds.extend( new GLatLng( venue.latitude, venue.longitude ) );
				set = true;
			}
		});
		if( set )
			GAsync( map, 'getBoundsZoomLevel', [bounds],
				function( zoom ) {
					ignoreNextZoom = true;
					map.setCenter( boundsCenter(bounds), zoom - 1 );
				});
	}
	catch( e ) {
	}
}

function load() {
	if( ! window.GBrowserIsCompatible() ) {
		alert( 'Sorry, this browser is not compatible with Google Maps' );
	}
	else {
		map = new GMap2( document.getElementById('map') );
		map.setCenter( new GLatLng( 37.5538, -122.2999 ), 8 );
		map.addControl( new GSmallMapControl );
		map.addControl( new GMapTypeControl );
		if( ! narrow ) map.addControl( new GScaleControl );
		//map.addControl( new GOverviewMapControl );
		map.enableDoubleClickZoom();
		map.enableContinuousZoom();
		start();
	}
}

function unload() {
	GUnload();
}

var $where;
function start() {
	if( ! mapplet )
		showDetail = hoverize( showDetail );
	
	$where = $('#ZventsWhere');
	if( $where.e.value )
		submitSearch();
	else
		mapMoved();
	
	/*
	function logListener( name ) {
		GEvent.addListener( map, name, function() {
			console.debug( name );
		});
	}
	
	logListener( 'dragstart' );
	logListener( 'dragend' );
	logListener( 'movestart' );
	logListener( 'moveend' );
	logListener( 'zoomend' );
	logListener( 'infowindowopen' );
	logListener( 'infowindowclose' );
	*/
	
	GEvent.addListener( map, 'infowindowopen', function() {
		infoWindowOpen = true;
		if( mapMoving )
			infoWindowMoving = true;
	});
	
	GEvent.addListener( map, 'infowindowclose', function() {
		infoWindowOpen = infoWindowMoving = /*myInfoWindow =*/ false;
	});
	
	GEvent.addListener( map, 'mouseover', function() {
		showDetail.hover( null );
	});
	
	GEvent.addListener( map, 'mouseout', function() {
		showDetail.clear();
	});
	
	GEvent.addListener( map, 'movestart', function() {
		mapMoving = true;
	});
	
	GEvent.addListener( map, 'moveend', function() {
		if( ignoreNextZoom ) {
			ignoreNextZoom = false;
			return;
		}
		
		if( ! infoWindowMoving ) {
			$where.e.value = '';
			throttle( mapMoved );
		}
		mapMoving = infoWindowMoving = false;
	});
	
	$wrapper = $('#ZventsCalendarLoaderWrapper'), wrapper = $wrapper.e;
	
	if( ! mapplet )  {
		function setViewHeight() {
			var vp = $.viewport();
			var adjust = narrow ? 10 : 5;
			wrapper.style.height = ( $.viewport().cy - wrapper.offsetTop - adjust ) + 'px';
		}
		
		setViewHeight();
		$(window).on( 'resize', function() {
			//window.console && console.debug( 'Resize!' );
			//wrapper.style.height =
			setViewHeight();
			map.checkResize();
		});
	}
	
	//window.console && console.debug( 'Init!' );
}

</script>

]]></Content>
</Module>
