/*
**
**	GalleryView - jQuery Content Gallery Plugin
**	Author: 	Jack Anderson
**	Version:	3.0b3 (March 15, 2011)
**	
**	Please use this development script if you intend to make changes to the
**	plugin code.  For production sites, it is recommended you use jquery.galleryview-3.0.min.js.
**	
**  See README.txt for instructions on how to markup your HTML
**
**	See CHANGELOG.txt for a review of changes and LICENSE.txt for the applicable
**	licensing information.
**
*/

// Make sure Object.create is available in the browser (for our prototypal inheritance)
// Courtesy of Papa Crockford
if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

(function ($) {
	
	/*
	**	gvImage - Object
	**		Stores information for the images in the gallery
	*/
		var gvImage = function() {
			this.src = { panel: null, frame: null };
			this.height = 0;
			this.width = 0;
			this.scale = { panel: null, frame: null };
			this.attrs = {};
			this.href = null;
			this.dom_obj = null;
		};
	/*
	**	extraWidth(jQuery element)
	**		Return the combined width of the border and padding to the elements left and right.
	**		If the border is non-numerical, assume zero (not ideal, will fix later)
	**		RETURNS - int
	*/

		function extraWidth(el) {
			if (!el) {
				return 0;
			}
			if (el.length == 0) {
				return 0;
			}
			el = el.eq(0);
			var ew = 0;
			ew += getInt(el.css('paddingLeft'));
			ew += getInt(el.css('paddingRight'));
			ew += getInt(el.css('borderLeftWidth'));
			ew += getInt(el.css('borderRightWidth'));
			return ew;
		};
	/*
	**	extraHeight(jQuery element)
	**		Return the combined height of the border and padding above and below the element
	**		If the border is non-numerical, assume zero (not ideal, will fix later)
	**		RETURN - int
	*/

		function extraHeight(el) {
			if (!el) {
				return 0;
			}
			if (el.length == 0) {
				return 0;
			}
			el = el.eq(0);
			var eh = 0;
			eh += getInt(el.css('paddingTop'));
			eh += getInt(el.css('paddingBottom'));
			eh += getInt(el.css('borderTopWidth'));
			eh += getInt(el.css('borderBottomWidth'));
			return eh;
		};
	
	/*
	**	getInt(string)
	**		Parse a string to obtain the integer value contained
	**		If the string contains no number, return zero
	**		RETURN - int
	*/

		function getInt(i) {
			i = parseInt(i, 10);
			if (isNaN(i)) {
				i = 0;
			}
			return i;
		};
	/*
	**	gvGallery - Object
	**		The main gallery class
	*/		
		var gvGallery = {
		/*
		**	Default Options
		**		Object literal storing default plugin options
		*/
			options: {
				// General Options
				transition_speed: 500, 			//INT - duration of panel/frame transition (in milliseconds)
				transition_interval: 5000, 		//INT - delay between panel/frame transitions (in milliseconds)
				easing: 'swing', 				//STRING - easing method to use for animations (jQuery provides 'swing' or 'linear', more available with jQuery UI or Easing plugin)
				pause_on_hover: false, 			//BOOLEAN - flag to pause slideshow when user hovers over the gallery
				
				// Panel Options
				show_panels: true, 				//BOOLEAN - flag to show or hide panel portion of gallery
				show_panel_nav: true, 			//BOOLEAN - flag to show or hide panel navigation buttons
				show_overlays: true, 			//BOOLEAN - flag to show or hide panel overlays
				panel_width: 900, 				//INT - width of gallery panel (in pixels)
				panel_height: 220, 				//INT - height of gallery panel (in pixels)
				panel_animation: 'fade', 		//STRING - animation method for panel transitions (crossfade,fade,slide,zoomOut,none)
				panel_scale: 'crop', 			//STRING - cropping option for panel images (crop = scale image and fit to aspect ratio determined by panel_width and panel_height, nocrop = scale image and preserve original aspect ratio)
				overlay_opacity: 0.8, 			//FLOAT - transparency for panel overlay (1.0 = opaque, 0.0 = transparent)
				overlay_position: 'bottom', 	//STRING - position of panel overlay (bottom, top)
				pan_images: true,
				
				// Filmstrip Options
				start_frame: 1, 				//INT - index of panel/frame to show first when gallery loads
				show_filmstrip: true, 			//BOOLEAN - flag to show or hide filmstrip portion of gallery
				show_filmstrip_nav: true, 		//BOOLEAN - flag indicating whether to display navigation buttons
				show_captions: false, 			//BOOLEAN - flag to show or hide frame captions	
				filmstrip_size: 3, 				//INT - number of frames to show in filmstrip-only gallery
				filmstrip_style: 'scroll', 		//STRING - type of filmstrip to use (scroll, show all)
				filmstrip_position: 'bottom', 	//STRING - position of filmstrip within gallery (bottom, top, left, right)
				frame_width: 60, 				//INT - width of filmstrip frames (in pixels)
				frame_height: 30, 				//INT - width of filmstrip frames (in pixels)
				frame_opacity: 0.4, 			//FLOAT - transparency of non-active frames (1.0 = opaque, 0.0 = transparent)
				frame_scale: 'crop', 			//STRING - cropping option for filmstrip images (same as above)
				frame_gap: 5, 					//INT - spacing between frames within filmstrip (in pixels)
				
				// Info Bar Options
				infobar_opacity: 1				//FLOAT - transparency for info bar
			},
		/*
		**	init(JSON,DOMElement)
		**		Initialize gallery using provided options
		*/
			init: function (options, el) {
				var self = this;
				
				this.opts = $.extend({},self.options,options);
				
				this.el = el;					// DOMElement - Holds a reference to the DOM element that called the plugin
				this.$el = $(el);				// jQuery Object - Holds a jQuery reference to the DOM element that called the plugin
				this.id; 						// STRING - id attribute of element passed to plugin
				this.iterator = 0; 				// INT - Currently visible panel/frame
				this.item_count = 0; 			// INT - Total number of panels/frames
				this.slide_method; 				// STRING - indicator to slide entire filmstrip not ('strip','static')
				this.paused = true; 			// BOOLEAN - flag to indicate whether automated transitions are active
				this.animate_panels = true; 	// BOOLEAN - flag to indicate whether panels will animate during transitions
				this.current = 1; 				// INT - index of current panel/frame
				this.gallery_images; 			// OBJECT - container for images within UL passed to plugin
				this.image_count = 0; 			// INT - number of images within gallery
				this.loaded_images = 0; 		// INT - number of gallery images that have been loaded in the browser
				this.first_loaded = false;		// BOOLAN - flag to indicate whether start frame has loaded or not
				this.nav_heights = new Array();	// ARRAY - holds the heights of each navigation button
				this.nav_widths = new Array();	// ARRAY - holds the widths of each navigation button
				this.gvImages = new Array();	// ARRAY - holds a collection of gvImage objects
				this.mouse = {x: 0, y: 0};		// OBJECT - contains location of pointer 
					
				// Element dimensions
				this.gallery_width;
				this.gallery_height;
				this.strip_width;
				this.strip_height;
				this.wrapper_width;
				this.wrapper_height;
				this.f_frame_width;
				this.f_frame_height;
				this.filmstrip_orientation;
				
				// Flag indicating whether to scale panel images
				this.scale_panel_images = true;
				this.panel_nav_displayed = false;
				
				// Define jQuery objects for reuse
				this.j_gallery;
				this.j_filmstrip;
				this.j_frames;
				this.j_frame_img_wrappers;
				this.j_panels;
				this.j_panel_wrapper;
				this.j_info;

				// Get dimensions of all navigation buttons
				var temp = $('<div></div>');
				temp.hide().appendTo('body');
				
				temp.attr('class', 'gv-nav-next');
				self.nav_widths['next'] = temp.width();
				self.nav_heights['next'] = temp.height();
				
				temp.attr('class', 'gv-nav-prev');
				self.nav_widths['prev'] = temp.width();
				self.nav_heights['prev'] = temp.height();
				
				temp.attr('class', 'gv-nav-play');
				self.nav_widths['play'] = temp.width();
				self.nav_heights['play'] = temp.height();
				
				temp.attr('class', 'gv-panel-nav-next');
				self.nav_widths['pnext'] = temp.width();
				self.nav_heights['pnext'] = temp.height();
				
				temp.attr('class', 'gv-panel-nav-next');
				self.nav_widths['pprev'] = temp.width();
				self.nav_heights['pprev'] = temp.height();
				
				temp.remove();
				
				// Calculate largest dimensions and sum of all dimensions for later use
				self.nav_widths['max'] = Math.max(self.nav_widths['next'],self.nav_widths['prev'],self.nav_widths['play']);
				self.nav_heights['max'] = Math.max(self.nav_heights['next'],self.nav_heights['prev'],self.nav_heights['play']);
				
				self.nav_widths['all'] = self.nav_widths['next'] + self.nav_widths['prev'] + self.nav_widths['play'];
				self.nav_heights['all'] = self.nav_heights['next'] + self.nav_heights['prev'] + self.nav_heights['play'];
				
				// Set values to zero if we hide filmstrip navigation,
				// makes calculations easier later on
				if(!self.opts.show_filmstrip_nav) {
					self.nav_widths['max'] = 0;
					self.nav_widths['all'] = 0;
					self.nav_heights['max'] = 0;
					self.nav_heights['all'] = 0;
				}
				
				// Hide the unstyled UL until we've created the gallery
				self.$el.css('display', 'none');
				
				// Set the current frame index to that chosen by the user
				// current is 0-based and self.opts.start_frame is 1-based
				self.current = self.opts.start_frame - 1;
				
				// Wrap UL in two DIVs (gallery DIV and gallery container DIV) and transfer ID to gallery DIV
				self.$el.wrap("<div></div>");
				self.j_gallery = self.$el.parent();
				self.j_gallery.css('display', 'none').attr('id', self.$el.attr('id')).addClass('gv-gallery').addClass(self.$el.attr('class'));
				self.j_gallery.wrap('<div class="gv-gallery-container"></div>');
				
				// Save the id of the UL passed to the plugin
				self.id = self.j_gallery.attr('id');
				
				
				// Assign filmstrip class to the UL sent to the plugin
				self.$el.removeAttr('id').addClass('gv-filmstrip');
				
				// If the transition or pause timers exist for any reason, stop them now.
				$(document).stopTime("transition_" + self.id);
				$(document).stopTime("animation_pause_" + self.id);
				
				
				// If the UL does not contain any <div class="gv-panel-content"> elements, we will scale the UL images to fill the panels
				self.scale_panel_images = $('.gv-panel-content', self.j_gallery).length == 0;
				
				// Set flag for animating panels
				self.animate_panels = (self.opts.panel_animation != 'none');
				
				// Determine filmstrip orientation (vertical or horizontal)
				self.filmstrip_orientation = (self.opts.filmstrip_position == 'top' || self.opts.filmstrip_position == 'bottom' ? 'horizontal' : 'vertical');
				
				// Do not show captions on vertical filmstrips (override user set option)
				if (self.filmstrip_orientation == 'vertical') {
					self.opts.show_captions = false;
				}
				
				// Assign elements to variables to minimize calls to jQuery
				self.j_filmstrip = $('.gv-filmstrip', self.j_gallery);
				self.j_frames = $('li', self.j_filmstrip);
				self.j_frames.addClass('gv-frame gv-frame-loading');
				self.j_panel_wrapper = $('<div>');
				self.j_panel_wrapper.addClass('gv-panel_wrap gv-panel-loading').prependTo(self.j_gallery);
				
				// Reference images for later use (if there are no content panels)
				if (self.j_frames.find('.gv-panel-content').length == 0) {
					self.gallery_images = $('img', self.$el);
					self.image_count = self.gallery_images.length;
					self.gvImages = new Array(self.image_count);
					self.gallery_images.each(function (i,el) {				   
						var img = $(this);
						self.gvImages[i] = new gvImage();
						var gvi = self.gvImages[i];
						gvi.src.panel = gvi.src.frame = img.attr('src');
						gvi.attrs.title = img.attr('title');	
						if (img.parent('a').length > 0) {
							gvi.href = img.parent('a').attr('href');
							img.parent('a').remove();
						}
						img.remove();
					});
				}
				
				// If the user wants panels, generate them now
				if (self.opts.show_panels) {
					for (i = self.j_frames.length - 1; i >= 0; i--) {
						jf = self.j_frames.eq(i);
						
						// If we find content panels, move them from the filmstrip to the panel wrapper
						// Otherwise, generate panel DIVs
						if (jf.find('.gv-panel-content').length > 0) {
							jf.find('.gv-panel-content').remove().prependTo(self.j_panel_wrapper).addClass('gv-panel').addClass(jf.attr('class')).removeClass('gv-frame');
						} else {
							p = $('<div>');
							p.addClass('gv-panel');
							
							// Copy over any user-generated classes
							p.addClass(jf.attr('class')).removeClass('gv-frame').removeClass('gv-frame-loading');
							p.prependTo(self.j_panel_wrapper);
							self.j_frames.eq(i).find('.gv-panel-overlay').remove().appendTo(p);
						}
					}
				} else {
					
					// If we are not displaying panels, remove them from the source code now
					$('.gv-panel-overlay', self.j_frames).remove();
					$('.gv-panel-content', self.j_frames).remove();
				}
				
				// If the user doesn't want a filmstrip, delete it
				if (!self.opts.show_filmstrip) {
					self.j_filmstrip.remove();
				} else {
					
					// Wrap the frame images (and links, if applicable) in container divs
					// These divs will handle cropping and zooming of the images
					self.j_frames.each(function (i) {
						if ($(this).find('a').length > 0) {
							$(this).find('a').wrap('<div class="gv-img_wrap"></div>');
						} else {
							$(this).append('<div class="gv-img_wrap"></div>');
						}
					});
					self.j_frame_img_wrappers = $('.gv-img_wrap', self.j_frames);
				}
				self.j_panels = $('.gv-panel', self.j_gallery);
				if (!self.opts.show_panels) {
					self.opts.panel_height = 0;
					self.opts.panel_width = 0;
				}
				
				// Briefly create a caption element so galleryView can determine any padding or borders applied by the user
				$('<div class="gv-caption"></div>').appendTo(self.j_frames);
				
				// Determine final frame dimensions, accounting for user-added padding and border
				self.f_frame_width = self.opts.frame_width + extraWidth(self.j_frame_img_wrappers);
				self.f_frame_height = self.opts.frame_height + extraHeight(self.j_frame_img_wrappers);
				
				frame_caption_size = getInt($('.gv-caption', self.j_gallery).css('height'));
				f_caption_width = self.f_frame_width - extraWidth($('.gv-caption', self.j_gallery));
				f_caption_height = frame_caption_size + extraHeight($('.gv-caption', self.j_gallery));
				
				// Delete the temporary caption element
				$('.gv-caption', self.j_gallery).remove();
				
				// Number of frames in filmstrip
				self.item_count = self.opts.show_panels ? self.j_panels.length : self.j_frames.length;
				
				// Number of frames that can display within the gallery block
				if (self.filmstrip_orientation == 'horizontal') {
					strip_size = self.opts.show_panels ? Math.floor((self.opts.panel_width + self.opts.frame_gap - (!self.opts.show_filmstrip_nav ? 0 : (self.opts.frame_gap * 2) + self.nav_widths['all'])) / (self.f_frame_width + self.opts.frame_gap)) : Math.min(self.item_count, self.opts.filmstrip_size);
				} else {
					strip_size = self.opts.show_panels ? Math.floor((self.opts.panel_height + self.opts.frame_gap - (!self.opts.show_filmstrip_nav ? 0 : self.opts.frame_gap + self.nav_heights['max'])) / (self.f_frame_height + self.opts.frame_gap)) : Math.min(self.item_count, self.opts.filmstrip_size);
				}
				
				// Determine animation method for filmstrip
				// If more items than strip size, slide filmstrip
				// Otherwise, keep it static
				if (strip_size >= self.item_count) {
					self.slide_method = 'static';
					strip_size = self.item_count;
				} else {
					self.slide_method = 'strip';
				}
				
				self.iterator = self.opts.start_frame - 1;
				
				if (self.opts.filmstrip_style == 'scroll' && strip_size < self.item_count) {
					self.iterator += self.item_count;
				}
				
				// Determine dimensions of various gallery elements
				self.j_filmstrip.css('margin', 0);
				
				// Width of filmstrip
				if (self.filmstrip_orientation == 'horizontal') {
					if (self.opts.filmstrip_style == 'show all' || (self.opts.filmstrip_style == 'scroll' && self.slide_method == 'static')) {
						self.strip_width = (self.f_frame_width * strip_size) + (self.opts.frame_gap * (strip_size));
					} else {
						self.strip_width = (self.f_frame_width * self.item_count * 3) + (self.opts.frame_gap * ((self.item_count * 3)));
					}
				} else {
					if (self.opts.filmstrip_style == 'show all') {
						self.strip_width = (self.f_frame_width * Math.ceil(self.item_count / strip_size)) + (self.opts.frame_gap * (Math.ceil(self.item_count / strip_size)));
					} else {
						self.strip_width = (self.f_frame_width);
					}
				}
				
				// Height of filmstrip
				if (self.filmstrip_orientation == 'horizontal') {
					if (self.opts.filmstrip_style == 'show all') {
						self.strip_height = ((self.f_frame_height + (self.opts.show_captions ? f_caption_height : 0)) * Math.ceil(self.item_count / strip_size)) + (self.opts.frame_gap * (Math.ceil(self.item_count / strip_size) - 1));
					} else {
						self.strip_height = (self.f_frame_height + (self.opts.show_captions ? f_caption_height : 0));
					}
				} else {
					if (self.opts.filmstrip_style == 'show all' || (self.opts.filmstrip_style == 'scroll' && self.slide_method == 'static')) {
						self.strip_height = ((self.f_frame_height * strip_size) + self.opts.frame_gap * (strip_size - 1));
					} else {
						self.strip_height = (self.f_frame_height * self.item_count * 3) + (self.opts.frame_gap * ((self.item_count * 3) - 1));
					}
				}
				
				// Width of filmstrip wrapper (to hide overflow)
				if (self.filmstrip_orientation == 'horizontal') {
					self.wrapper_width = ((strip_size * self.f_frame_width) + ((strip_size - 1) * self.opts.frame_gap));
					if (self.opts.filmstrip_style == 'show all') {
						self.wrapper_height = ((self.f_frame_height + (self.opts.show_captions ? f_caption_height : 0)) * Math.ceil(self.item_count / strip_size)) + (self.opts.frame_gap * (Math.ceil(self.item_count / strip_size) - 1));
					} else {
						self.wrapper_height = (self.f_frame_height + (self.opts.show_captions ? f_caption_height : 0));
					}
				} else {
					self.wrapper_height = ((strip_size * self.f_frame_height) + ((strip_size - 1) * self.opts.frame_gap));
					if (self.opts.filmstrip_style == 'show all') {
						self.wrapper_width = (self.f_frame_width * Math.ceil(self.item_count / strip_size)) + (self.opts.frame_gap * (Math.ceil(self.item_count / strip_size) - 1));
					} else {
						self.wrapper_width = self.f_frame_width;
					}
				}
				
				// There shouldn't be any padding on the gallery element
				self.j_gallery.css('padding', 0);
				
				// Determine final dimensions of gallery
				if (self.filmstrip_orientation == 'horizontal') {
					
					// Width of gallery block
					self.gallery_width = self.opts.show_panels ? self.opts.panel_width : self.wrapper_width + self.nav_widths['all'] + (self.opts.frame_gap * 2);
					
					// Height of gallery block
					self.gallery_height = (self.opts.show_panels ? self.opts.panel_height + (self.opts.show_filmstrip ? self.opts.frame_gap : 0) : 0) + (self.opts.show_filmstrip ? Math.max(self.wrapper_height, self.nav_heights['max']) : 0);
				} else {
					
					// Height of gallery block
					self.gallery_height = self.opts.show_panels ? self.opts.panel_height : self.wrapper_height + (self.f_frame_width < self.nav_widths['all'] ? self.nav_heights['all'] : self.nav_heights['max']);
					
					// Width of gallery block
					self.gallery_width = (self.opts.show_panels ? self.opts.panel_width + (self.opts.show_filmstrip ? self.opts.frame_gap : 0) : 0) + (self.opts.show_filmstrip ? Math.max(self.wrapper_width, (self.f_frame_width < self.nav_widths['all'] ? self.nav_widths['max'] : self.nav_widths['all'])) : 0);
				}
				
				// We don't want to move to the next frame before the current one is done loading
				// If the animation speed is greater than the delay between animations, set them equal
				if (self.opts.transition_speed > self.opts.transition_interval && self.opts.transition_interval > 0) {
					self.opts.transition_speed = self.opts.transition_interval;
				}
				self.buildGallery();
				
				$('#'+self.id).data('galleryView',self);
				return self;
			},
		/*
		**	showItem(int,boolean,function)
		**		Transition from current frame to frame i (1-based index)
		**		skip_animation flag let's us override transition speed to show an item instantly
		**		(useful when loading gallery for the first time)
		**		If provided, run callback function after transition completes
		*/
			showItem: function (i, speed, callback) {
				var self = this;
				// A scrolling filmstrip will contain three copies of each frame, we want to know the relative position of the target frame
				var mod_i = i % self.item_count;
				var distance;
				var diststr;
				
				// Disable next/prev buttons until transition is complete
				// This prevents overlapping of animations
				$('.gv-nav-next, .gv-panel-nav-next, .gv-nav-prev, .gv-panel-nav-prev', self.j_gallery).unbind('click');
				self.j_frames.unbind('click');
				
				$(document).stopTime('hideInfoBar_' + self.id);
				self.j_info.html(((i%self.item_count) + 1) + ' z ' + self.item_count);
				self.j_info.stop().css('display','block').animate({ opacity: self.opts.infobar_opacity }, 5);
				$(document).oneTime(speed+2000,'hideInfoBar_' + self.id,function() {
					self.j_info.fadeOut(1000);
				});
				
				// Use timer to rebind navigation buttons when transition ends
				$(document).oneTime(speed, 'bindNavButtons_' + self.id, function () {
					$('.gv-nav-next, .gv-panel-nav-next', self.j_gallery).click(self.showNextItem);
					$('.gv-nav-prev, .gv-panel-nav-prev', self.j_gallery).click(self.showPrevItem);
					self.enableFrameClicking();
				});
				
				if (self.opts.show_filmstrip) {
					
					// Fade out all frames
					self.j_frames.removeClass('current').find('img').stop().animate({
						opacity: self.opts.frame_opacity
					}, speed);
					
					// Fade in target frame
					self.j_frames.eq(i).addClass('current').find('img').stop().animate({
						opacity: 1
					}, speed);
				}
				
				//If necessary, transition between panels
				if (self.opts.show_panels) {
					if (self.animate_panels) {
						if (self.opts.panel_animation == 'slide') {
							
							// Move target frame just to the right of the current frame
							if ((self.opts.filmstrip_style == 'show all' || self.slide_method == 'static') && i < self.current && self.paused) {
								self.j_panels.eq(mod_i).css({
									left: -(getInt($('.gv-panel.current').eq(0).css('left')) + self.opts.panel_width) + 'px',
									zIndex: 50
								}).show().animate({
									left: '+=' + self.opts.panel_width + 'px'
								}, speed, self.opts.easing, function () {
									$(this).addClass('current');
								});
								$('.gv-panel.current').css({
									zIndex: 49
								}).animate({
									left: '+=' + self.opts.panel_width + 'px'
								}, speed, self.opts.easing, function () {
									$(this).removeClass('current').hide();
								});
							} else {
								self.j_panels.eq(mod_i).css({
									left: getInt($('.gv-panel.current').eq(0).css('left')) + self.opts.panel_width + 'px',
									zIndex: 50
								}).show().animate({
									left: '-=' + self.opts.panel_width + 'px'
								}, speed, self.opts.easing, function () {
									$(this).addClass('current');
								});
								$('.gv-panel.current').css({
									zIndex: 49
								}).animate({
									left: '-=' + self.opts.panel_width + 'px'
								}, speed, self.opts.easing, function () {
									$(this).removeClass('current').hide();
								});
							}
						} else if (self.opts.panel_animation == 'zoomOut') {
							
							// After zoom is complete, add 'current' class to now visible panel and move it to top of stack via z-index
							$(document).oneTime(speed, 'setCurrentFrame_' + self.id, function () {
								self.j_panels.eq(mod_i).addClass('current').css('zIndex', 50);
							});
							
							// Show target panel and place below current frame via z-index
							self.j_panels.eq(mod_i).show().css('zIndex', 49);
							
							// Shrink panel container to center while moving image in opposite direction
							// End result is an image that remains static while borders of container shrink
							$('.gv-panel.current img').animate({
								top: '-=' + self.opts.panel_height / 2 + 'px',
								left: '-=' + self.opts.panel_width / 2 + 'px'
							}, speed, 'swing', function () {
								
								// After zoom is complete, immediately animate it back to its original position for next time
								$(this).animate({
									top: '+=' + self.opts.panel_height / 2 + 'px',
									left: '+=' + self.opts.panel_width / 2 + 'px'
								}, 0);
							});
							$('.gv-panel.current').animate({
								top: '+=' + self.opts.panel_height / 2 + 'px',
								left: '+=' + self.opts.panel_width / 2 + 'px',
								height: 0,
								width: 0
							}, speed, 'swing', function () {
								$(this).removeClass('current').hide().css({
									top: self.getPos(self.j_panels[mod_i]).top + 'px',
									left: self.getPos(self.j_panels[mod_i]).left + 'px',
									height: self.opts.panel_height + 'px',
									width: self.opts.panel_width + 'px'
								});
							});
						} else if (self.opts.panel_animation == 'crossfade') {
							
							// Default behavior is to fade panel into view
							self.j_panels.removeClass('current').fadeOut(speed, function () {
								$(this).css('filter', '');
							}).eq(mod_i).addClass('current').fadeIn(speed, function () {
								$(this).css('filter', '');
							});
						} else {
							
							// Fade out panels, and then fade in target panel
							// Use timer due to inconsistency in fadeIn/fadeOut callback reliability
							self.j_panels.removeClass('current').stop().fadeOut(10);
							$(document).oneTime(10, 'fadeInPanel_' + self.id, function () {
								self.j_panels.eq(mod_i).addClass('current').stop().fadeIn(speed - 10);
							});
						}
					} else {
						
						// If no animation style is chosen, simply show the new panel instantly
						$(document).oneTime(speed, 'switch_panels_' + self.id, function () {
							self.j_panels.hide().eq(mod_i).show();
						});
					}
				}
				
				// If gallery has a filmstrip, handle animation of frames
				if (self.opts.show_filmstrip) {
					
					// Slide filmstrip or don't, depending on transition method
					if (self.opts.filmstrip_style == 'scroll' && self.slide_method == 'strip') {
						
						// Stop filmstrip if it's currently in motion
						self.j_filmstrip.stop();
						if (self.filmstrip_orientation == 'horizontal') {
							
							// Determine distance between current frame (eventual destination) and target frame
							distance = self.getPos(self.j_frames[i]).left - self.getPos($('.gv-strip_wrapper', self.j_gallery)[0]).left;
							diststr = (distance >= 0 ? '-=' : '+=') + Math.abs(distance) + 'px';
							
							// Animate filmstrip and slide target frame to destination
							self.j_filmstrip.animate({
								left: diststr
							}, speed, self.opts.easing, function () {
								var old_i = i;
								
								// After transition is complete, shift filmstrip so that a sufficient number of frames
								// remain on either side of the visible filmstrip
								if (i > self.item_count) {
									i = mod_i;
									self.iterator = i;
									self.j_filmstrip.css('left', '-' + ((self.f_frame_width + self.opts.frame_gap) * i) + 'px');
								} else if (i <= (self.item_count - strip_size)) {
									i = (mod_i) + self.item_count;
									self.iterator = i;
									self.j_filmstrip.css('left', '-' + ((self.f_frame_width + self.opts.frame_gap) * i) + 'px');
								}
								
								// If the target frame has changed due to filmstrip shifting,
								// make sure new target frame has 'current' class and correct size/opacity settings
								if (old_i != i) {
									self.j_frames.eq(old_i).removeClass('current').find('img').css({
										opacity: self.opts.frame_opacity
									});
									self.j_frames.eq(i).addClass('current').find('img').css({
										opacity: 1
									});
								}
							});
						} else {
							
							//Determine distance between current frame (eventual destination) and target frame
							distance = self.getPos(self.j_frames[i]).top - self.getPos($('.gv-strip_wrapper', self.j_gallery)[0]).top;
							diststr = (distance >= 0 ? '-=' : '+=') + Math.abs(distance) + 'px';
							
							// Animate filmstrip and slide target frame to destination
							self.j_filmstrip.animate({
								'top': diststr
							}, speed, self.opts.easing, function () {
								
								// After transition is complete, shift filmstrip so that a sufficient number of frames
								// remain on either side of the visible filmstrip
								var old_i = i;
								if (i > self.item_count) {
									i = mod_i;
									self.iterator = i;
									self.j_filmstrip.css('top', '-' + ((self.f_frame_height + self.opts.frame_gap) * i) + 'px');
								} else if (i <= (self.item_count - strip_size)) {
									i = (mod_i) + self.item_count;
									self.iterator = i;
									self.j_filmstrip.css('top', '-' + ((self.f_frame_height + self.opts.frame_gap) * i) + 'px');
								}
								
								//If the target frame has changed due to filmstrip shifting,
								//Make sure new target frame has 'current' class and correct size/opacity settings
								if (old_i != i) {
									self.j_frames.eq(old_i).removeClass('current').find('img').css({
										opacity: self.opts.frame_opacity
									});
									self.j_frames.eq(i).addClass('current').find('img').css({
										opacity: 1
									});
								}
								
								// If panels are not set to fade in/out, simply hide all panels and show the target panel
								if (!self.animate_panels) {
									self.j_panels.hide().eq(mod_i).show();
								}
							});
						}
					}
				}
				if (callback) {
					$(document).oneTime(speed, 'this.showItemCallback_' + self.id, callback);
				}
				self.current = i;
			},
		/*
		**	showNextItem()
		**		Transition from current frame to next frame
		*/
			showNextItem: function () {
				// This function is called by an element within a gallery, retrive the gvGallery object
				var self = $(this).parents('.gv-gallery').data('galleryView');
				// Cancel any transition timers until we have completed this function
				$(document).stopTime("transition_" + self.id);
				if (++self.iterator == self.j_frames.length) {
					self.iterator = 0;
				}
				
				// We've already written the code to transition to an arbitrary panel/frame, so use it
				self.showItem(self.iterator, self.opts.transition_speed);
				
				// If automated transitions haven't been cancelled by an option or paused on hover, re-enable them
				if (!self.paused && self.opts.transition_interval > 0) {
					$(document).everyTime(self.opts.transition_interval, "transition_" + self.id, function () {
						$('#'+self.id+' .gv-nav-next').trigger('click');
					});
				}
			},
		/*
		**	showPrevItem()
		**		Transition from current frame to previous frame
		*/
			showPrevItem: function () {
				// This function is called by an element within a gallery, retrive the gvGallery object
				var self = $(this).parents('.gv-gallery').data('galleryView');
				// Cancel any transition timers until we have completed this function
				$(document).stopTime("transition_" + self.id);
				if (--self.iterator < 0) {
					self.iterator = self.item_count - 1;
				}
				
				// We've already written the code to transition to an arbitrary panel/frame, so use it
				self.showItem(self.iterator, self.opts.transition_speed);
				
				// If automated transitions haven't been cancelled by an option or paused on hover, re-enable them
				if (!self.paused && self.opts.transition_interval > 0) {
					$(document).everyTime(self.opts.transition_interval, "transition_" + self.id, function () {
						$('#'+self.id+' .gv-nav-next').trigger('click');
					});
				}
			},
		/*
		**	startSlideshow()
		**		Begin automated transitions for gallery
		*/
			startSlideshow: function () {
				var self = $(this).parents('.gv-gallery').data('galleryView');
				$(document).everyTime(self.opts.transition_interval, "transition_" + self.id, function () {
					$('#'+self.id+' .gv-nav-next').trigger('click');
				});
				$('.gv-nav-play', self.j_gallery).addClass('gv-nav-pause').removeClass('gv-nav-play').click(self.stopSlideshow);
				self.paused = false;
			},
		/*
		**	stopSlideshow()
		**		Stop automated transitions for gallery
		*/
			stopSlideshow: function () {
				var self = $(this).parents('.gv-gallery').data('galleryView');
				$(document).stopTime("transition_" + self.id);
				$('.gv-nav-pause', self.j_gallery).addClass('gv-nav-play').removeClass('gv-nav-pause').click(self.startSlideshow);
				self.paused = true;
			},
		/*
		**	getPos(jQuery element)
		**		Calculate position of an element relative to top/left corner of gallery
		**		If the gallery bounding box itself is passed to the function, calculate position of gallery relative to top/left corner of browser window
		** 		RETURNS - JSON {left: int, top: int}
		*/

			getPos: function (el) {
				var self = this;
				if (!el) return {
					top: 0,
					left: 0
				};
				var left = 0,
					top = 0;
				var el_id = el.id;
				if (el.offsetParent) {
					do {
						left += el.offsetLeft;
						top += el.offsetTop;
					} while (el = el.offsetParent);
				}
				
				//If we want the position of the gallery itself, return it
				if (el_id == self.id) {
					return {
						'left': left,
						'top': top
					};
				}
				
				//Otherwise, get position of element relative to gallery
				else {
					var gPos = self.getPos(self.j_gallery[0]);
					var gLeft = gPos.left;
					var gTop = gPos.top;
					return {
						'left': left - gLeft,
						'top': top - gTop
					};
				}
			},
		/*
		**	mouseIsOverGallery(int,int)
		**		Check to see if mouse coordinates lie within borders of gallery
		**		This is a more reliable method than using the mouseover event
		**		RETURN - boolean
		*/
			mouseIsOverGallery: function (x, y) {
				var self = this;
				var pos = self.getPos(self.j_gallery[0]);
				var top = pos.top;
				var left = pos.left;
				return x > left && x < left + self.j_gallery.outerWidth() && y > top && y < top + self.j_gallery.outerHeight();
			},
		/*
		**	mouseIsOverPanel(int,int)
		**		Check to see if mouse coordinates lie within borders of panel
		**		This is a more reliable method than using the mouseover event
		**		RETURN - boolean
		*/
			mouseIsOverPanel: function (x, y) {
				var self = this;
				// Get position of panel wrapper in relation to gallery
				var pos = self.getPos($('#' + self.id + ' .gv-panel_wrap')[0]);
				var gPos = self.getPos(self.j_gallery[0]);
				
				// Add wrap position to gallery position
				var top = pos.top + gPos.top;
				var left = pos.left + gPos.left;
				return x > left && x < left + self.j_panels.outerWidth() && y > top && y < top + self.j_panels.outerHeight();
			},
		/*
		**	enableFrameClicking()
		**		Add an onclick event handler to each frame
		**		Exception: if a frame has an anchor tag, do not add an onlick handler
		*/
			enableFrameClicking: function () {
				var self = this;
				self.j_frames.each(function (i) {
					if ($('a', this).length == 0) {
						$(this).click(function () {
							// Prevent transitioning to the current frame (unnecessary)
							if (self.iterator != i) {
								$(document).stopTime("transition_" + self.id);
								self.showItem(i, self.opts.transition_speed);
								self.iterator = i;
								if (!self.paused && self.opts.transition_interval > 0) {
									$(document).everyTime(self.opts.transition_interval, "transition_" + self.id, function () {
										self.showNextItem();
									});
								}
							}
						});
					}
				});
			},
		/*
		**	this.buildPanels()
		**		Construct gallery panels: <div class="gv-panel"> elements
		**		NOTE - These DIVs are generated automatically from the content of the UL passed to the plugin
		*/
			buildPanels: function () {
				var self = this;
				// If panel overlay content exists, add the necessary overlay background DIV
				// The overlay content and background are separate elements so the background's opacity isn't inherited by the content
				self.j_panels.each(function (i) {
					if ($('.gv-panel-overlay', this).length > 0) {
						$(this).append('<div class="gv-overlay-background"></div>');
					}
				});
				
				// If desired, add navigation buttons to the panel itself
				if (self.opts.show_panel_nav) {
					$('<div></div>').addClass('gv-panel-nav-next').appendTo(self.j_gallery).css({
						position: 'absolute',
						zIndex: '1100',
						top: ((self.opts.filmstrip_position == 'top' ? self.opts.frame_gap + Math.max(self.wrapper_height, self.nav_heights['max']) : 0) + (self.opts.panel_height - self.nav_heights['pnext']) / 2) + 'px',
						right: ((self.opts.filmstrip_position == 'right' ? self.opts.frame_gap + Math.max(self.wrapper_width, (self.f_frame_width < self.nav_widths['all'] ? self.nav_widths['max'] : self.nav_widths['all'])) : 0) + 10) + 'px',
						display: 'none'
					}).click(self.showNextItem);
					$('<div></div>').addClass('gv-panel-nav-prev').appendTo(self.j_gallery).css({
						position: 'absolute',
						zIndex: '1100',
						top: ((self.opts.filmstrip_position == 'top' ? self.opts.frame_gap + Math.max(self.wrapper_height, self.nav_heights['max']) : 0) + (self.opts.panel_height - self.nav_heights['pprev']) / 2) + 'px',
						left: ((self.opts.filmstrip_position == 'left' ? self.opts.frame_gap + Math.max(self.wrapper_width, (self.f_frame_width < self.nav_widths['all'] ? self.nav_widths['max'] : self.nav_widths['all'])) : 0) + 10) + 'px',
						display: 'none'
					}).click(self.showPrevItem);
				}
				
				//Set size and position of panel container
				self.j_panel_wrapper.css({
					width: self.opts.panel_width + 'px',
					height: self.opts.panel_height + 'px',
					position: 'absolute',
					overflow: 'hidden'
				});
				if (self.opts.show_filmstrip) {
					switch (self.opts.filmstrip_position) {
					case 'top':
						self.j_panel_wrapper.css({
							top: Math.max(self.wrapper_height, self.nav_heights['max']) + self.opts.frame_gap + 'px'
						});
						break;
					case 'left':
						self.j_panel_wrapper.css({
							left: Math.max(self.wrapper_width, self.nav_widths['max']) + self.opts.frame_gap + 'px'
						});
						break;
					default:
						break;
					}
				}
				
				// Set the height and width of each panel, and position it appropriately within the gallery
				self.j_panels.each(function (i) {
					$(this).css({
						width: (self.opts.panel_width - extraWidth(self.j_panels)) + 'px',
						height: (self.opts.panel_height - extraHeight(self.j_panels)) + 'px',
						position: 'absolute',
						top: 0,
						left: 0,
						display: 'none'
					});
				});
				
				// Position each panel overlay within panel
				$('.gv-panel-overlay', self.j_panels).css({
					position: 'absolute',
					zIndex: '999',
					width: (self.opts.panel_width - extraWidth($('.gv-panel-overlay', self.j_panels))) + 'px',
					left: 0
				});
				$('.gv-overlay-background', self.j_panels).css({
					position: 'absolute',
					zIndex: '998',
					width: self.opts.panel_width + 'px',
					left: 0,
					opacity: self.opts.overlay_opacity
				});
				if (self.opts.overlay_position == 'top') {
					$('.gv-panel-overlay', self.j_panels).css('top', 0);
					$('.gv-overlay-background', self.j_panels).css('top', 0);
				} else {
					$('.gv-panel-overlay', self.j_panels).css('bottom', 0);
					$('.gv-overlay-background', self.j_panels).css('bottom', 0);
				}
				$('.gv-panel iframe', self.j_panels).css({
					width: self.opts.panel_width + 'px',
					height: self.opts.panel_height + 'px',
					border: 0
				});
				
				self.j_info = $('<div class="gv-infobar"></div>');
				self.j_info.html(self.opts.start_frame + ' of ' + self.item_count).appendTo(self.j_panel_wrapper);
				self.j_info.css({
					position: 'absolute',
					bottom: '0px',
					right: '0px',
					cursor: 'default',
					zIndex: 1000,
					opacity: self.opts.infobar_opacity
				});
			},
		/*
		**	this.buildFilmstrip()
		**		Construct filmstrip from <ul class="gv-filmstrip"> element
		**		NOTE - 'filmstrip' class is automatically added to UL passed to plugin
		*/
			buildFilmstrip: function () {
				var self = this;
				// Add wrapper to filmstrip to hide extra frames
				self.j_filmstrip.wrap('<div class="gv-strip_wrapper"></div>');
				
				// If we have to scroll the filmstrip, add two copies of each frame to the filmstrip
				if (self.opts.filmstrip_style == 'scroll' && self.slide_method == 'strip') {
					self.j_frames.clone().appendTo(self.j_filmstrip);
					self.j_frames.clone().appendTo(self.j_filmstrip);
					self.j_frames = $('li', self.j_filmstrip);
				}
				
				// If captions are enabled, add caption DIV to each frame and fill with the image titles
				if (self.opts.show_captions) {
					self.j_frames.append('<div class="gv-caption"></div>').each(function (i) {
						$(this).find('.gv-caption').html(self.gvImages[i % self.item_count].attrs.title);
					});
				}
				
				// Position the filmstrip within the strip wrapper
				self.j_filmstrip.css({
					listStyle: 'none',
					margin: 0,
					padding: 0,
					width: self.strip_width + 'px',
					position: 'absolute',
					zIndex: '900',
					top: (self.filmstrip_orientation == 'vertical' && self.opts.filmstrip_style == 'scroll' && self.slide_method == 'strip' ? -((self.f_frame_height + self.opts.frame_gap) * self.iterator) : 0) + 'px',
					left: (self.filmstrip_orientation == 'horizontal' && self.opts.filmstrip_style == 'scroll' && self.slide_method == 'strip' ? -((self.f_frame_width + self.opts.frame_gap) * self.iterator) : 0) + 'px',
					height: self.strip_height + 'px'
				});
				self.j_frames.css({
					float: 'left',
					position: 'relative',
					height: self.f_frame_height + (self.opts.show_captions ? f_caption_height : 0) + 'px',
					width: self.f_frame_width + 'px',
					zIndex: '901',
					padding: 0,
					marginBottom: self.opts.frame_gap + 'px',
					marginRight: self.opts.frame_gap + 'px'
				});
				
				
				// Set overflow of filmstrip wrapper to hidden so as to hide frames that don't fit within the gallery
				$('.gv-strip_wrapper', self.j_gallery).css({
					position: 'absolute',
					overflow: 'hidden'
				});
				
				// Position strip wrapper within gallery based on user options
				if (self.filmstrip_orientation == 'horizontal') {
					$('.gv-strip_wrapper', self.j_gallery).css({
						top: self.opts.show_panels ? self.opts.filmstrip_position == 'top' ? Math.max(((self.nav_heights['max'] - self.wrapper_height) / 2), 0) : self.opts.panel_height + self.opts.frame_gap + Math.max(((self.nav_heights['max'] - self.wrapper_height) / 2), 0) + 'px' : 0,
						left: '0px',
						width: self.wrapper_width + 'px',
						height: self.wrapper_height + 'px'
					});
				} else {
					$('.gv-strip_wrapper', self.j_gallery).css({
						left: self.opts.show_panels ? self.opts.filmstrip_position == 'left' ? Math.max(((self.nav_widths['max'] - self.wrapper_width) / 2), 0) : self.opts.panel_width + self.opts.frame_gap + Math.max(((self.nav_widths['max'] - self.wrapper_width) / 2), 0) + 'px' : 0,
						top: 0,
						width: self.wrapper_width + 'px',
						height: self.wrapper_height + 'px'
					});
				}
				
				// Style frame captions
				$('.gv-caption', self.j_gallery).css({
					position: 'absolute',
					top: (self.opts.filmstrip_position == 'bottom' ? self.f_frame_height : 0) + 'px',
					left: 0,
					margin: 0,
					width: f_caption_width + 'px',
					overflow: 'hidden'
				});
				
				// Add navigation buttons
				if (self.opts.show_filmstrip_nav) {
					var navNext = $('<div></div>');
					navNext.addClass('gv-nav-next').appendTo(self.j_gallery).css({
						position: 'absolute'
					}).click(self.showNextItem);
					var navPrev = $('<div></div>');
					navPrev.addClass('gv-nav-prev').appendTo(self.j_gallery).css({
						position: 'absolute'
					}).click(self.showPrevItem);
					var navPlay = $('<div></div>');
					navPlay.addClass('gv-nav-play').appendTo(self.j_gallery).css({
						position: 'absolute'
					}).click(self.startSlideshow);
					if (self.filmstrip_orientation == 'horizontal') {
						navNext.css({
							top: (self.opts.show_panels ? (self.opts.filmstrip_position == 'top' ? 0 : self.opts.panel_height + self.opts.frame_gap) : 0) + ((Math.max(self.strip_height, self.nav_heights['max']) - self.nav_heights['next']) / 2) + 'px',
							right: '0px'
						});
						navPrev.css({
							top: (self.opts.show_panels ? (self.opts.filmstrip_position == 'top' ? 0 : self.opts.panel_height + self.opts.frame_gap) : 0) + ((Math.max(self.strip_height, self.nav_heights['max']) - self.nav_heights['prev']) / 2) + 'px',
							right: (self.nav_widths['next'] + self.nav_widths['play']) + 'px'
						});
						navPlay.css({
							top: (self.opts.show_panels ? (self.opts.filmstrip_position == 'top' ? 0 : self.opts.panel_height + self.opts.frame_gap) : 0) + ((Math.max(self.strip_height, self.nav_heights['max']) - self.nav_heights['play']) / 2) + 'px',
							right: (self.nav_widths['next']) + 'px'
						});
					} else {
						navNext.css({
							left: (self.opts.show_panels ? (self.opts.filmstrip_position == 'left' ? 0 : self.opts.panel_width + self.opts.frame_gap) : 0) + ((Math.max(self.strip_width, self.nav_widths['max']) - self.nav_widths['next']) / 2) + (self.strip_width < (self.nav_widths['all']) ? 0 : (self.nav_widths['play'] / 2) + (self.nav_widths['next'] / 2)) + 'px',
							bottom: self.strip_width < self.nav_widths['all'] ? 0 : ((self.nav_heights['max'] - self.nav_heights['next']) / 2) + 'px'
						});
						navPrev.css({
							left: (self.opts.show_panels ? (self.opts.filmstrip_position == 'left' ? 0 : self.opts.panel_width + self.opts.frame_gap) : 0) + ((Math.max(self.strip_width, self.nav_widths['max']) - self.nav_widths['prev']) / 2) - (self.strip_width < (self.nav_widths['all']) ? 0 : (self.nav_widths['play'] / 2) + (self.nav_widths['prev'] / 2)) + 'px',
							bottom: (self.strip_width < self.nav_widths['all'] ? (self.nav_heights['next'] + self.nav_heights['play']) : ((self.nav_heights['max'] - self.nav_heights['prev']) / 2)) + 'px'
						});
						navPlay.css({
							left: (self.opts.show_panels ? (self.opts.filmstrip_position == 'left' ? 0 : self.opts.panel_width + self.opts.frame_gap) : 0) + ((Math.max(self.strip_width, self.nav_widths['max']) - self.nav_widths['play']) / 2) + 'px',
							bottom: (self.strip_width < self.nav_widths['all'] ? self.nav_heights['next'] : ((self.nav_heights['max'] - self.nav_heights['play']) / 2)) + 'px'
						});
					}
				}
			},
		/*
		**	initImages()
		**		Insert image objects into panels and frames, and then fade them in as they load
		*/
			initImages: function () {
				var self = this;
				// For each image in the original list, create a new image element
				for (i = 0; i < self.gvImages.length; i++) {
					gvi = self.gvImages[i];
					var img = self.gallery_images.eq(i);
					
					var _im = $('<img />');
					_im.hide();
					_im.attr('index', i);
					_im.data('galleryView',self);
					_im.bind('mousedown',function(e) {
						if (e.preventDefault) e.preventDefault();
					});
					_im.bind('load', function () {
						_g = $(this).data('galleryView');
						_g.loaded_images++;
						if(_g.loaded_images == _g.item_count) {
							$(document).oneTime(2000, 'hideInfoBar_' + _g.id, function() {
								_g.j_info.fadeOut(1000);
							});
						}
						
						// Once the image is loaded, we can work with it
						var _i = $(this).attr('index');
						gvi.height = this.height;
						gvi.width = this.width;
						
						// Calculate scaling/cropping figures
						if (_g.opts.frame_scale == 'nocrop') {
							gvi.scale.frame = Math.min(_g.opts.frame_height / gvi.height, _g.opts.frame_width / gvi.width);
						} else {
							gvi.scale.frame = Math.max(_g.opts.frame_height / gvi.height, _g.opts.frame_width / gvi.width);
						}
						if (_g.opts.panel_scale == 'nocrop') {
							gvi.scale.panel = Math.min(_g.opts.panel_height / gvi.height, _g.opts.panel_width / gvi.width);
						} else {
							gvi.scale.panel = Math.max(_g.opts.panel_height / gvi.height, _g.opts.panel_width / gvi.width);
						}
						var _parent = $(this).parents('div').eq(0);
						
						// Style the image accordingly, depending on where it resides
						if (_parent.hasClass('gv-panel') && _g.scale_panel_images) {
							
							if(self.opts.pan_images) {
								$(this).css('cursor','url(http://www.google.com/intl/en_ALL/mapfiles/openhand.cur), n-resize');	
							}
							$(this).css({
								height: gvi.scale.panel * gvi.height + 'px',
								width: gvi.scale.panel * gvi.width + 'px',
								position: 'relative',
								top: (_g.opts.panel_height - (gvi.scale.panel * gvi.height)) / 2 + 'px',
								left: (_g.opts.panel_width - (gvi.scale.panel * gvi.width)) / 2 + 'px'
							});
							if (_i == _g.iterator % _g.item_count) {
								$('.gv-panel_wrap', _g.j_gallery).removeClass('gv-panel-loading');
								if (!_g.first_loaded) {
									_g.showItem(_g.iterator, 5);
									_g.first_loaded = true;
								}
							}
						} else if (_parent.hasClass('gv-img_wrap')) {
							$(this).css({
								opacity: _g.opts.frame_opacity,
								height: gvi.scale.frame * gvi.height + 'px',
								width: gvi.scale.frame * gvi.width + 'px',
								position: 'relative',
								top: Math.min(0, (_g.opts.frame_height - (gvi.scale.frame * gvi.height)) / 2) + 'px',
								left: Math.min(0, (_g.opts.frame_width - (gvi.scale.frame * gvi.width)) / 2) + 'px'
							}).mouseover(function () {
								$(this).stop().animate({
									opacity: 1
								}, 300);
							}).mouseout(function () {
								
								//Don't fade out current frame on mouseout
								if (!$(this).parent().parent().hasClass('current')) {
									$(this).stop().animate({
										opacity: _g.opts.frame_opacity
									}, 300);
								}
							});
							_parent.css({
								height: Math.min(_g.opts.frame_height, gvi.scale.frame * gvi.height) + 'px',
								width: Math.min(_g.opts.frame_width, gvi.scale.frame * gvi.width) + 'px',
								position: 'relative',
								top: (_g.opts.show_captions && _g.opts.filmstrip_position == 'top' ? f_caption_height : 0) + Math.max(0, (_g.opts.frame_height - (gvi.scale.frame * gvi.height)) / 2) + 'px',
								left: Math.max(0, (_g.opts.frame_width - (gvi.scale.frame * gvi.width)) / 2) + 'px',
								overflow: 'hidden'
							});
							$(this).parents('.gv-frame').removeClass('gv-frame-loading');
							if (_i == _g.iterator % _g.item_count && !_g.first_loaded) {
								_g.showItem(_g.iterator, 5);
								_g.first_loaded = true;
							}
							
						}
						$(this).fadeIn('fast');
					});
					// If the image has a 'frame' attribute, load that image into the filmstrip frames
					if (img.attr('frame')) {
						gvi.src.frame = img.attr('frame');
					}
					_im.clone(true).appendTo($('.gv-img_wrap:eq(' + i + ')',self.j_gallery)).attr({
						src: gvi.src.frame,
						title: gvi.attrs.title
					});
					_im.clone(true).appendTo($('.gv-img_wrap:eq(' + (i + self.item_count) + ')',self.j_gallery)).attr({
						src: gvi.src.frame,
						title: gvi.attrs.title
					});
					_im.clone(true).appendTo($('.gv-img_wrap:eq(' + (i + (2 * self.item_count)) + ')',self.j_gallery)).attr({
						src: gvi.src.frame,
						title: gvi.attrs.title
					});
					
					_im.appendTo($('.gv-panel:eq(' + i + ')', self.j_gallery)).attr({
						src: gvi.src.panel,
						title: gvi.attrs.title
					});
				}
			},
		/*
		**	this.buildGallery()
		**		Construct HTML and CSS for the gallery, based on user options
		*/
			buildGallery: function () {
				var self = this;
				// Set gallery dimensions
				self.j_gallery.css({
					position: 'relative',
					width: self.gallery_width + 'px',
					height: self.gallery_height + 'px'
				});
				self.j_gallery.parent().css({
					width: self.gallery_width + 'px',
					height: self.gallery_height + 'px'
				});
				
				// Build filmstrip if necessary
				if (self.opts.show_filmstrip) {
					self.buildFilmstrip();
					self.enableFrameClicking();
				}
				
				//Strip out panel overlays if the user does not want to display them
				if (!self.opts.show_overlays) {
					$('.gv-panel-overlay', self.j_gallery).remove();
				}
				
				// Build panels if necessary
				if (self.opts.show_panels) {
					self.buildPanels();
				}
				self.initImages();
				
				$(document).mousedown(function(e) {
					self.is_mousedown = true;
					if (self.opts.pan_images && self.mouseIsOverPanel(e.pageX,e.pageY)) {
						$('.gv-panel img',self.j_gallery).css('cursor','url(http://www.google.com/intl/en_ALL/mapfiles/closedhand.cur), n-resize');
					}
				}).mouseup(function(e) {
					self.is_mousedown = false;
					if (self.opts.pan_images) {
						$('.gv-panel img',self.j_gallery).css('cursor','url(http://www.google.com/intl/en_ALL/mapfiles/openhand.cur), n-resize');
					}
				});
				
				// If user opts to pause on hover, or show panel navigation, add some mouseover functionality
				if (self.opts.pause_on_hover || self.opts.show_panel_nav) {
					$(document).mousemove(function (e) {
						
						if(self.opts.pan_images && self.is_mousedown && self.mouseIsOverPanel(e.pageX, e.pageY)) {
								var dist = e.pageY - self.mouse.y;
								var img = $('.gv-panel_wrap .current img', this.j_gallery);
								var new_top = parseInt(img.css('top'),10) + dist;
								if(new_top > 0) new_top = 0;
								if(new_top < -(img[0].height-self.opts.panel_height)) new_top = -(img[0].height-self.opts.panel_height);
								img.css('top',new_top);
						}
						
						// If user wants to pause on hover and mouse is over the gallery, halt any animations
						if (self.opts.pause_on_hover) {
							if (self.mouseIsOverGallery(e.pageX, e.pageY) && !self.paused) {
								
								// Pause slideshow in 500ms. This allows for brief swipes of the mouse over the gallery without unnecessarily pausing it
								$(document).oneTime(500, "animation_pause_" + self.id, function () {
									$(document).stopTime("transition_" + self.id);
									self.paused = true;
								});
							} else {
								$(document).stopTime("animation_pause_" + self.id);
								if (self.paused && self.opts.transition_interval > 0) {
									$(document).everyTime(self.opts.transition_interval, "transition_" + self.id, function () {
										self.showNextItem();
									});
									self.paused = false;
								}
							}
						}
						

						if (self.mouseIsOverPanel(e.pageX, e.pageY)) {
							if (self.opts.show_panel_nav && !self.panel_nav_displayed) {
								$('.gv-panel-nav-next, .gv-panel-nav-prev', self.j_gallery).show();
								self.panel_nav_displayed = self;
							}
							
							self.j_info.stop().css('display','block').animate({ opacity: self.opts.infobar_opacity }, 5);
							$(document).stopTime('hideInfoBar_' + self.id);
						} else {
							if (self.opts.show_panel_nav && self.panel_nav_displayed) {
								$('.gv-panel-nav-next, .gv-panel-nav-prev', self.j_gallery).hide();
								self.panel_nav_displayed = false;
							}
							
							$(document).oneTime(self.opts.transition_speed+2000, 'hideInfoBar_' + self.id, function() {
								self.j_info.fadeOut(1000);
							});
						}
						
						self.mouse = {x: e.pageX, y: e.pageY};
					});
				}
				
				// Display gallery
				self.j_filmstrip.css('display', 'block');
				self.j_gallery.css('display', 'block');
			}
			
		}; // END gvGallery
	
	/*
	**	MAIN PLUGIN CODE
	*/
		$.fn.galleryView = function (options) {
			if (this.length) {
				return this.each(function () {
					var myGallery = Object.create(gvGallery);
					myGallery.init(options,this);
				});
			}
		};
})(jQuery);
