// -----------------------------------------------------------------------------------
// 
// Simple Lightbox
// by Archetyped - http://archetyped.com/tools/simple-lightbox/
// Updated: 2011-01-27
//
//	Originally based on Lightbox Slideshow v1.1
//	by Justin Barkhuff - http://www.justinbarkhuff.com/lab/lightbox_slideshow/
//  2007/08/15
//
//	Largely based on Lightbox v2.02
//	by Lokesh Dhakar - http://huddletogether.com/projects/lightbox2/
//	2006/03/31
//
//	Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
//
// -----------------------------------------------------------------------------------
/**
 * Lightbox object
 */
//var SLB = null;
(function($) {
SLB = {
	activeImage : null,
	badObjects : ['select','object','embed'],
	container : null,
	enableSlideshow : null,
	groupName : null,
	imageArray : [],
	options : null,
	overlayDuration : null,
	overlayOpacity : null,
	playSlides : null,
	refTags : ['a'],
	relAttribute : null,
	resizeDuration : null,
	slideShowTimer : null,
	startImage : null,
	prefix : '',
	checkedUrls : {},
	media : {},
	
	/**
	 * Initialize lightbox instance
	 * @param object options Instance options
	 */
	initialize: function(options) {
		this.options = $.extend(true, {
			animate : true, // resizing animations
			validateLinks : false, //Validate links before adding them to lightbox
			captionEnabled: true, //Display caption
			captionSrc : true, //Use image source URI if title not set
			descEnabled: true, //Display description
			autoPlay : true, // should slideshow start automatically
			borderSize : 10, // if you adjust the padding in the CSS, you will need to update this variable
			containerID : document, // lightbox container object
			enableSlideshow : true, // enable slideshow feature
			googleAnalytics : false, // track individual image views using Google Analytics
			imageDataLocation : 'south', // location of image caption information
			initImage : '', // ID of image link to automatically launch when upon script initialization
			loop : true, // whether to continuously loop slideshow images
			overlayDuration : .2, // time to fade in shadow overlay
			overlayOpacity : .8, // transparency of shadow overlay
			relAttribute : null, // specifies the rel attribute value that triggers lightbox
			resizeSpeed : 400, // controls the speed of the image resizing (milliseconds)
			showGroupName : false, // show group name of images in image details
			slideTime : 4, // time to display images during slideshow
			altsrc : 'src',
			mId : 'id',
			strings : { // allows for localization
				closeLink : 'close',
				loadingMsg : 'loading',
				nextLink : 'next &raquo;',
				prevLink : '&laquo; prev',
				startSlideshow : 'start slideshow',
				stopSlideshow : 'stop slideshow',
				numDisplayPrefix : 'Image',
				numDisplaySeparator : 'of'
			},
			placeholders : {
				slbContent: '<img id="slb_slbContent" />',
				slbLoading: '<span id="slb_slbLoading">loading</span>',
				slbClose: '<a class="slb_slbClose" href="#">close</a>',
				navPrev: '<a class="slb_navPrev slb_nav" href="#">&laquo; prev</a>',
				navNext: '<a class="slb_navNext slb_nav" href="#">&raquo; next</a>',
				navSlideControl: '<a class="slb_navSlideControl" href="#">Stop</a>',
				dataCaption: '<span class="slb_dataCaption"></span>',
				dataDescription: '<span class="slb_dataDescription"></span>',
				dataNumber: '<span class="slb_dataNumber"></span>'
			},
			layout : null
        }, options);
		
		//Stop if no layout is defined
		if (!this.options.layout || this.options.layout.toString().length == 0)
			this.end();
		
		
		//Validate options
		if ( 'prefix' in this.options )
			this.prefix = this.options.prefix;
		  //Activation Attribute
		if ( null == this.options.relAttribute )
			 this.options.relAttribute = [this.prefix];
		else if ( !$.isArray(this.options.relAttribute) )
			this.options.relAttribute = [this.options.relAttribute.toString()];
		this.relAttribute = this.options.relAttribute;
		
		if ( this.options.animate ) {
			this.overlayDuration = Math.max(this.options.overlayDuration,0);
			this.resizeDuration = this.options.resizeSpeed;
		} else {
			this.overlayDuration = 0;
			this.resizeDuration = 0;
		}
		this.enableSlideshow = this.options.enableSlideshow;
		this.overlayOpacity = Math.max(Math.min(this.options.overlayOpacity,1),0);
		this.playSlides = this.options.autoPlay;
		this.container = $(this.options.containerID);
		this.updateImageList();
		var t = this;
		var objBody = $(this.container).get(0) != document ? this.container : $('body');
		
		var objOverlay = $('<div/>', {
			'id': this.getID('overlay'),
			'css': {'display': 'none'}
		}).appendTo(objBody)
		  .click(function() {t.end()});
		
		var objLightbox = $('<div/>', {
			'id': this.getID('lightbox'),
			'css': {'display': 'none'}
		}).appendTo(objBody)
		  .click(function() {t.end()});
		
		//Build layout from template
		var layout = this.getLayout();
		
		//Append to container
		$(layout).appendTo(objLightbox);
		
		//Set UI
		this.setUI();
		
		//Add events
		this.setEvents();
		
		if (this.options.initImage != '') {
			this.start($(this.options.initImage));
		}
	},
	
	/**
	 * Build layout from template
	 * @uses options.layout
	 * @return string Layout markup (HTML)
	 */
	getLayout: function() {
		var l = this.options.layout;
		
		//Expand placeholders
		var ph, phs, phr;
		for (ph in this.options.placeholders) {
			phs = '{' + ph + '}';
			//Continue to next placeholder if current one is not in layout
			if (l.indexOf(phs) == -1)
				continue;
			phr = new RegExp(phs, "g");
			l = l.replace(phr, this.options.placeholders[ph]);
		}
		
		//Return final layout
		return l;
		
	},
	
	/**
	 * Set localized values for UI elements
	 */
	setUI: function() {
		var s = this.options.strings;
		this.get('slbClose').html(s.closeLink);
		this.get('navNext').html(s.nextLink);
		this.get('navPrev').html(s.prevLink);
		this.get('navSlideControl').html(((this.playSlides) ? s.stopSlideshow : s.startSlideshow));
	},
	
	/**
	 * Add events to various UI elements
	 */
	setEvents: function() {
		var t = this, delay = 500;
		this.get('container,details').click(function(ev) {
			ev.stopPropagation();
		});
		
		var clickP = function() {
			t.get('navPrev').unbind('click').click(false);
			setTimeout(function() {t.get('navPrev').click(clickP)}, delay);
			t.showPrev();
			return false;
		};
		this.get('navPrev').click(function(){
			return clickP();
		});
		
		var clickN = function() {
			t.get('navNext').unbind('click').click(false);
			setTimeout(function() {t.get('navNext').click(clickN)}, delay);
			t.showNext();
			return false;
		};
		this.get('navNext').click(function() {
			return clickN();
		});
		
		this.get('navSlideControl').click(function() {
			t.toggleSlideShow();
			return false;
		});
		this.get('slbClose').click(function() {
			t.end();
			return false;
		});
	},
	
	/**
	 * Finds all compatible image links on page
	 */
	updateImageList: function() {
		var el, els, rel, ph = '{relattr}', t = this;
		var sel = [], selBase = '[href][rel*="' + ph + '"]:not([rel~="' + this.addPrefix('off') + '"])';
		
		//Define event handler
		var handler = function() {
			//Check if element is valid for lightbox
			t.start(this);
			return false;
		};
		
		//Build selector
		for (var i = 0; i < this.refTags.length; i++) {
			for (var x = 0; x < this.relAttribute.length; x++) {
				sel.push(this.refTags[i] + selBase.replace(ph, this.relAttribute[x]));
			}
		}
		sel = sel.join(',');
		//Add event handler to links
		$(sel, $(this.container)).live('click', handler);
	},
	
	/**
	 * Display overlay and lightbox. If image is part of a set, add siblings to imageArray.
	 * @param node imageLink Link element containing image URL
	 */
	start: function(imageLink) {
		imageLink = $(imageLink);
		this.hideBadObjects();

		this.imageArray = [];
		this.groupName = this.getGroup(imageLink);
		
		var rel = $(imageLink).attr('rel') || '';
		var imageTitle = '';
		var t = this;
		var groupTemp = {};
		this.fileExists(this.getSourceFile(imageLink),
		function() { //File exists
			// Stretch overlay to fill page and fade in
			t.get('overlay')
				.height($(document).height())
				.fadeTo(t.overlayDuration, t.overlayOpacity);
			
			// Add image to array closure
			var addLink = function(el, idx) {
				groupTemp[idx] = el;
				return groupTemp.length;
			};
			
			//Build final image array & launch lightbox
			var proceed = function() {
				t.startImage = 0;
				//Sort links by document order
				var order = [], el;
				for (var x in groupTemp) {
					order.push(x);
				}
				order.sort(function(a, b) { return (a - b); });
				for (x = 0; x < order.length; x++) {
					el = groupTemp[order[x]];
					//Check if link being evaluated is the same as the clicked link
					if ($(el).get(0) == $(imageLink).get(0)) {
						t.startImage = x;
					}
					t.imageArray.push({'link':t.getSourceFile($(el)), 'title':t.getCaption(el), 'desc': t.getDescription(el)});
				}
				// Calculate top offset for the lightbox and display 
				var lightboxTop = $(document).scrollTop() + ($(window).height() / 15);
		
				t.get('lightbox').css('top', lightboxTop + 'px').show();
				t.changeImage(t.startImage);
			}
			
			// If image is NOT part of a group..
			if (null == t.groupName) {
			// Add single image to imageArray
				addLink(imageLink, 0);			
				t.startImage = 0;
				proceed();
			} else {
				// If image is part of a group
				var els = $(t.container).find($(imageLink).get(0).tagName.toLowerCase());
				// Loop through links on page & find other images in group
				var grpLinks = [];
				var i, el;
				for (i = 0; i < els.length; i++) {
					el = $(els[i]);
					if (t.getSourceFile(el) && (t.getGroup(el) == t.groupName)) {
						//Add links in same group to temp array
						grpLinks.push(el);
					}
				}
				
				//Loop through group links, validate, and add to imageArray
				var processed = 0;
				for (i = 0; i < grpLinks.length; i++) {
					el = grpLinks[i];
					t.fileExists(t.getSourceFile($(el)),
						function(args) { //File exists
							var el = args.els[args.idx];
							var il = addLink(el, args.idx);
							processed++;
							if (processed == args.els.length)
								proceed();
						},
						function(args) { //File does not exist
							processed++;
							if (args.idx == args.els.length)
								proceed(); 
						},
						{'idx': i, 'els': grpLinks});
				}
			}	
		},
		function() { //File does not exist
			t.end();
		});
	},
	
	/**
	 * Retrieve ID of media item
	 * @param {Object} el Link element
	 * @return int Media ID (Default: 0 - No ID)
	 */
	getMediaId: function(el) {
		var rel = $(el).attr('rel') || '',
			mId = 0;
		if (rel.length) {
			var reId = new RegExp('\\b' + this.addPrefix(this.options.mId) + '\\[(.+?)\\](?:\\b|$)');
			if (reId.test(rel)) {
				mId = reId.exec(rel)[1];
			}
		}
		return mId;
	},
	
	/**
	 * Retrieve Media properties
	 * @param {Object} el Link element
	 * @return Object (Default: Empty)
	 */
	getMediaProperties: function(el) {
		var props = {},
			mId = this.getMediaId(el);
		if (mId in this.media) {
			props = this.media[mId];
		}
		return props;
	},
	
	/**
	 * Retrieve single property for media item
	 * @param {Object} el Link element
	 * @param string prop Property to retrieve
	 * @return mixed Item property (Default: false)
	 */
	getMediaProperty: function(el, prop) {
		var props = this.getMediaProperties(el);
		return (prop in props) ? props[prop] : false;
	},
	
	/**
	 * Build caption for displayed caption
	 * @param {Object} imageLink
	 */
	getCaption: function(imageLink) {
		imageLink = $(imageLink);
		var caption = '';
		if (this.options.captionEnabled) {
			var sels = {
				'capt': '.wp-caption-text',
				'gIcon': '.gallery-icon'
			};
			var els = {
				'link': imageLink,
				'origin': imageLink,
				'sibs': null,
				'img': null
			}
			//WP Caption
			if ( $(els.link).parent(sels.gIcon).length > 0 ) {
				els.origin = $(els.link).parent();
			}
			if ( (els.sibs = $(els.origin).siblings(sels.capt)) && $(els.sibs).length > 0 ) {
				caption = $(els.sibs).first().text();
			}
			caption = caption.trim();
			//Fall back to image properties
			if ( '' == caption ) {
				els.img = $(els.link).find('img').first();
				if ( $(els.img).length ) {
					//Image title / alt
					caption = $(els.img).attr('title') || $(els.img).attr('alt');
				}
			}
			caption = caption.trim();
			//Fall back Link Text
			if ('' == caption) {
				if ($(sels.link).text().trim().length) {
					caption = $(sels.link).text().trim();
				} else if (this.options.captionSrc) {
					//Fall back to Link href
					caption = $(sels.link).attr('href');
				}
			}
			caption = caption.trim();
		}
		return caption;
	},
	
	/**
	 * Retrieve item description
	 * @param {Object} imageLink
	 * @return string Item description (Default: empty string)
	 */
	getDescription: function(imageLink) {
		var desc = '';
		if (this.options.descEnabled) {
			//Retrieve description
			desc = this.getMediaProperty(imageLink, 'desc');
			if (!desc)
				desc = '';
		}
		return desc;
	},
	
	/**
	 * Retrieve source URI in link
	 * @param {Object} el
	 * @return string Source file URI
	 */
	getSourceFile: function(el) {
		var src = $(el).attr('href');
		var rel = $(el).attr('rel') || '';
		if (rel.length) {
			//Attachment source
			relSrc = this.getMediaProperty(el, 'source');
			//Explicit source
			if (!relSrc || !relSrc.length) {
				var reSrc = new RegExp('\\b' + this.addPrefix(this.options.altsrc) + '\\[(.+?)\\](?:\\b|$)');
				if (reSrc.test(rel)) {
					relSrc = reSrc.exec(rel)[1];
				}
			}
			//Set source using rel-derived value
			if ( relSrc.length )
				src = relSrc;
		}
		return src;
	},
	
	/**
	 * Extract group name from 
	 * @param obj el Element to extract group name from
	 * @return string Group name
	 */
	getGroup: function(el) {
		//Get full attribute value
		var g = null;
		var rel = $(el).attr('rel') || '';
		if (rel != '') {
			var gTmp = '',
				gSt = '[',
				gEnd = ']',
				search = '',
				idx,
				prefix = ' ';
			//Iterate through attributes to find group
			for (var i = 0; i < this.relAttribute.length; i++) {
				search = this.relAttribute[i];
				idx = rel.indexOf(search);
				//Prefix with space to find whole word
				if (prefix != search.charAt(0) && idx > 0) {
					search = prefix + search;
					idx = rel.indexOf(search);
				}
				//Stop processing if value is not found
				if (idx == -1)
					continue;
				gTmp = $.trim(rel.substring(idx).replace(search, ''));
				//Check if group defined
				if (gTmp.length && gSt == gTmp.charAt(0) && gTmp.indexOf(gEnd) != -1) {
					//Extract group name
					g = gTmp.substring(1, gTmp.indexOf(gEnd));
					continue;
				}
			}
		}
		return g;
	},

	/**
	 * Preload requested image prior to displaying it in lightbox 
	 * @param int imageNum Index of image in imageArray property
	 * @uses imageArray to retrieve index at specified image
	 * @uses resizeImageContainer() to resize lightbox after image has loaded
	 */
	changeImage: function(imageNum) {
		this.activeImage = imageNum;

		this.disableKeyboardNav();
		this.pauseSlideShow();

		// hide elements during transition
		this.get('slbLoading').show();
		this.get('slbContent').hide();
		this.get('details').hide();
		var imgPreloader = new Image();
		var t = this;
		// once image is preloaded, resize image container
		$(imgPreloader).bind('load', function() {
			t.get('slbContent').attr('src', imgPreloader.src);
			t.resizeImageContainer(imgPreloader.width, imgPreloader.height);
			//Restart slideshow if active
			if ( t.isSlideShowActive() )
				t.startSlideShow();
		});
		
		//Load image
		imgPreloader.src = this.imageArray[this.activeImage].link;
	},

	/**
	 * Resizes lightbox to fit image
	 * @param int imgWidth Image width in pixels
	 * @param int imgHeight Image height in pixels
	 */
	resizeImageContainer: function(imgWidth, imgHeight) {
		// get current height and width
		var el = this.get('container');
		var borderSize = this.options.borderSize * 2;
		
		this.get('container').animate({width: imgWidth + borderSize, height: imgHeight + borderSize}, this.resizeDuration)

		this.showImage();
	},
	
	/**
	 * Display image and begin preloading neighbors.
	 */	
	showImage: function() {
		this.get('slbLoading').hide();
		var t = this;
		this.get('slbContent').fadeIn(500, function() { t.updateDetails(); });
		this.preloadNeighborImages();
	},

	/**
	 * Display caption, image number, and bottom nav
	 */
	updateDetails: function() {
		//Caption
		if (this.options.captionEnabled) {
			this.get('dataCaption').text(this.imageArray[this.activeImage].title);
			this.get('dataCaption').show();
		} else {
			this.get('dataCaption').hide();
		}
		
		//Description
		console.dir(this.imageArray[this.activeImage]);
		this.get('dataDescription').text(this.imageArray[this.activeImage].desc);
		
		// if image is part of set display 'Image x of y' 
		if (this.hasImages()) {
			var num_display = this.options.strings.numDisplayPrefix + ' ' + (this.activeImage + 1) + ' ' + this.options.strings.numDisplaySeparator + ' ' + this.imageArray.length;
			if (this.options.showGroupName && this.groupName != '') {
				num_display += ' ' + this.options.strings.numDisplaySeparator + ' ' + this.groupName;
			}
			this.get('dataNumber')
				.text(num_display)
				.show();
		}
	
		this.get('details').width(this.get('slbContent').width() + (this.options.borderSize * 2));
		this.updateNav();
		var t = this;
		this.get('details').animate({height: 'show', opacity: 'show'}, 650);
	},
	
	/**
	 * Display appropriate previous and next hover navigation.
	 */
	updateNav: function() {
		if (this.hasImages()) {
			this.get('navPrev').show();
			this.get('navNext').show();
			if (this.enableSlideshow) {
				this.get('navSlideControl').show();
				if (this.playSlides) {
					this.startSlideShow();
				} else {
					this.stopSlideShow();
				}
			} else {
				this.get('navSlideControl').hide();
			}
		} else {
			// Hide navigation controls when only one image exists
			this.get('dataNumber').hide();
			this.get('navPrev').hide();
			this.get('navNext').hide();
			this.get('navSlideControl').hide();
		}
		this.enableKeyboardNav();
	},
	
	/**
	 * Checks if slideshow is currently activated
	 * @return bool TRUE if slideshow is active, FALSE otherwise
	 * @uses playSlides to check slideshow activation status
	 */
	isSlideShowActive: function() {
		return this.playSlides;
	},
	
	/**
	 * Start the slideshow
	 */
	startSlideShow: function() {
		this.playSlides = true;
		var t = this;
		clearInterval(this.slideShowTimer);
		this.slideShowTimer = setInterval(function() { t.showNext(); t.pauseSlideShow(); }, this.options.slideTime * 1000);
		this.get('navSlideControl').text(this.options.strings.stopSlideshow);
	},
	
	/**
	 * Stop the slideshow
	 */
	stopSlideShow: function() {
		this.playSlides = false;
		if (this.slideShowTimer) {
			clearInterval(this.slideShowTimer);
		}
		this.get('navSlideControl').text(this.options.strings.startSlideshow);
	},

	/**
	 * Toggles the slideshow status
	 */
	toggleSlideShow: function() {
		if (this.playSlides) {
			this.stopSlideShow();
		}else{
			this.startSlideShow();
		}
	},

	/**
	 * Pauses the slideshow
	 * Stops the slideshow but does not change the slideshow's activation status
	 */
	pauseSlideShow: function() {
		if (this.slideShowTimer) {
			clearInterval(this.slideShowTimer);
		}
	},
	
	/**
	 * Check if there is at least one image to display in the lightbox
	 * @return bool TRUE if at least one image is found
	 * @uses imageArray to check for images
	 */
	hasImage: function() {
		return ( this.imageArray.length > 0 );
	},
	
	/**
	 * Check if there are multiple images to display in the lightbox
	 * @return bool TRUE if there are multiple images
	 * @uses imageArray to determine the number of images
	 */
	hasImages: function() {
		return ( this.imageArray.length > 1 );
	},
	
	/**
	 * Check if the current image is the first image in the list
	 * @return bool TRUE if image is first
	 * @uses activeImage to check index of current image
	 */
	isFirstImage: function() {
		return ( this.activeImage == 0 );
	},
	
	/**
	 * Check if the current image is the last image in the list
	 * @return bool TRUE if image is last
	 * @uses activeImage to check index of current image
	 * @uses imageArray to compare current image to total number of images
	 */
	isLastImage: function() {
		return ( this.activeImage == this.imageArray.length - 1 );
	},
	
	/**
	 * Show the next image in the list
	 */
	showNext : function() {
		if (this.hasImages()) {
			if ( !this.options.loop && this.isLastImage() ) {
				return this.end();
			}
			if ( this.isLastImage() ) {
				this.showFirst();
			} else {
				this.changeImage(this.activeImage + 1);
			}
		}
	},

	/**
	 * Show the previous image in the list
	 */
	showPrev : function() {
		if (this.hasImages()) {
			if ( !this.options.loop && this.isFirstImage() )
				return this.end();
			if (this.activeImage == 0) {
				this.showLast();
			} else {
				this.changeImage(this.activeImage - 1);
			}
		}
	},
	
	/**
	 * Show the first image in the list
	 */
	showFirst : function() {
		if (this.hasImages()) {
			this.changeImage(0);
		}
	},

	/**
	 * Show the last image in the list
	 */
	showLast : function() {
		if (this.hasImages()) {
			this.changeImage(this.imageArray.length - 1);
		}
	},

	/**
	 * Enable image navigation via the keyboard
	 */
	enableKeyboardNav: function() {
		var t = this;
		$(document).keydown(function(e) {
			t.keyboardAction(e);
		});
	},

	/**
	 * Disable image navigation via the keyboard
	 */
	disableKeyboardNav: function() {
		$(document).unbind('keydown');
	},

	/**
	 * Handler for keyboard events
	 * @param event e Keyboard event data
	 */
	keyboardAction: function(e) {
		if (e == null) { // ie
			keycode = event.keyCode;
		} else { // mozilla
			keycode = e.which;
		}

		key = String.fromCharCode(keycode).toLowerCase();

		if (keycode == 27 || key == 'x' || key == 'o' || key == 'c') { // close lightbox
			this.end();
		} else if (key == 'p' || key == '%') { // display previous image
			this.showPrev();
		} else if (key == 'n' || key =='\'') { // display next image
			this.showNext();
		} else if (key == 'f') { // display first image
			this.showFirst();
		} else if (key == 'l') { // display last image
			this.showLast();
		} else if (key == 's') { // toggle slideshow
			if (this.hasImage() && this.options.enableSlideshow) {
				this.toggleSlideShow();
			}
		}
	},

	/**
	 * Preloads images before/after current image
	 */
	preloadNeighborImages: function() {
		var nextImageID = this.imageArray.length - 1 == this.activeImage ? 0 : this.activeImage + 1;
		nextImage = new Image();
		nextImage.src = this.imageArray[nextImageID].link;

		var prevImageID = this.activeImage == 0 ? this.imageArray.length - 1 : this.activeImage - 1;
		prevImage = new Image();
		prevImage.src = this.imageArray[prevImageID].link;
	},

	/**
	 * Close the lightbox
	 */
	end: function() {
		this.disableKeyboardNav();
		this.pauseSlideShow();
		this.get('lightbox').hide();
		this.get('overlay').fadeOut(this.overlayDuration);
		this.showBadObjects();
	},
	
	/**
	 * Displays objects that may conflict with the lightbox
	 * @param bool show (optional) Whether or not to show objects (Default: TRUE)
	 */
	showBadObjects: function (show) {
		show = ( typeof(show) == 'undefined' ) ? true : !!show;
		var vis = (show) ? 'visible' : 'hidden';
		$(this.badObjects.join(',')).css('visibility', vis);
	},
	
	/**
	 * Hides objects that may conflict with the lightbox
	 * @uses showBadObjects() to hide objects
	 */
	hideBadObjects: function () {
		this.showBadObjects(false);
	},

	/**
	 * Generate separator text
	 * @param string sep Separator text
	 * @return string Separator text
	 */
	getSep: function(sep) {
		return ( typeof sep == 'undefined' ) ? '_' : sep;
	},
	
	/**
	 * Retrieve prefix
	 * @return string Object prefix
	 */
	getPrefix: function() {
		return this.prefix;
	},

	/**
	 * Add prefix to text
	 * @param string txt Text to add prefix to
	 * @param string sep (optional) Separator text
	 * @return string Prefixed text
	 */
	addPrefix: function(txt, sep) {
		return this.getPrefix() + this.getSep(sep) + txt;
	},
	
	hasPrefix: function(txt) {
		return ( txt.indexOf(this.addPrefix('')) == 0 ) ? true : false;
	},
	
	/**
	 * Generate formatted ID for lightbox-specific elements
	 * @param string id Base ID of element
	 * @return string Formatted ID
	 */
	getID: function(id) {
		return this.addPrefix(id);
	},
	
	/**
	 * Generate formatted selector for lightbox-specific elements
	 * Compares specified ID to placeholders first, then named elements
	 * Multiple selectors can be included and separated by commas (',')
	 * @param string id Base ID of element
	 * @uses options.placeholders to compare id to placeholder names
	 * @return string Formatted selector
	 */
	getSel: function(id) {
		//Process multiple selectors
		var delim = ',', prefix = '#', sel;
		if (id.toString().indexOf(delim) != -1) {
			//Split
			var sels = id.toString().split(delim);
			//Build selector
			for (var x = 0; x < sels.length; x++) {
				sels[x] = this.getSel($.trim(sels[x]));
			}
			//Join
			sel = sels.join(delim);
		} else {
			//Single selector
			if (id in this.options.placeholders) {
				var ph = $(this.options.placeholders[id]);
				if (!ph.attr('id')) {
					//Class selector
					prefix = '.';
				}
			}
			sel = prefix + this.getID(id);
		}
		
		return sel;
	},
	
	/**
	 * Retrieve lightbox-specific element
	 * @param string id Base ID of element
	 * @uses getSel() to generate formatted selector for element
	 * @return object jQuery object of selected element(s)
	 */
	get: function(id) {
		return $(this.getSel(id));
	},
	
	/**
	 * Checks if file exists using AJAX request
	 * @param string url File URL
	 * @param callback success Callback to run if file exists
	 * @param callback failure Callback to run if file does not exist
	 * @param obj args Arguments for callback
	 */
	fileExists: function(url, success, failure, args) {
		if (!this.options.validateLinks)
			return success(args);
		var statusFail = 400;
		var stateCheck = 4;
		var t = this;
		var proceed = function(res) {
			if (res.status < statusFail) {
				if ($.isFunction(success)) 
					success(args);
			} else {
				if ($.isFunction(failure)) 
					failure(args);
			}
		};
		
		//Check if URL already processed
		if (url in this.checkedUrls) {
			proceed(this.checkedUrls[url]);
		} else {
			var req = new XMLHttpRequest();
			req.open('HEAD', url, true);
			req.onreadystatechange = function() {
				if (stateCheck == this.readyState) {
					t.addUrl(url, this);
					proceed(this);
				}
			};
			req.send();
		}
	},
	
	addUrl: function(url, res) {
		if (!(url in this.checkedUrls))
			this.checkedUrls[url] = res;
	}
}
})(jQuery);

(function($){$(document).ready(function(){SLB.initialize({'validateLinks':false,'autoPlay':false,'slideTime':'6','loop':true,'overlayOpacity':'0.8','animate':false,'captionEnabled':true,'captionSrc':true,'layout':'<div id="slb_container"><div id="slb_content">{slbContent}<div id="slb_nav_hover">{navPrev}{navNext}</div><div id="slb_loading">{slbLoading}</div></div></div><div id="slb_details"><div id="slb_data"><div id="slb_data_content">{dataCaption}<span id="slb_data_desc">{dataDescription}</span>{dataNumber}<span id="slb_nav">{navPrev}{navNext}{navSlideControl}</span></div><div id="slb_close">{slbClose}</div></div></div>','altsrc':'slb_src','relAttribute':['slb'],'prefix':'slb','strings':{'closeLink':'close','loadingMsg':'loading','nextLink':'next &raquo;','prevLink':'&laquo; prev','startSlideshow':'start slideshow','stopSlideshow':'stop slideshow','numDisplayPrefix':'Image','numDisplaySeparator':'of'}});})})(jQuery);

/* Ultimate Fade-in slideshow (v2.4)
* Last updated: May 24th, 2010. This notice must stay intact for usage 
* Author: Dynamic Drive at http://www.dynamicdrive.com/
* Visit http://www.dynamicdrive.com/ for full source code
*/

//Oct 6th, 09' (v2.1): Adds option to randomize display order of images, via new option displaymode.randomize
//May 24th, 10' (v2.4): Adds new "peakaboo" option to "descreveal" setting. oninit and onslide event handlers added.

var fadeSlideShow_descpanel={
	controls: [['x.png',7,7], ['restore.png',10,11], ['loading.gif',54,55]], //full URL and dimensions of close, restore, and loading images
	fontStyle: 'normal 11px Verdana', //font style for text descriptions
	slidespeed: 200 //speed of description panel animation (in millisec)
}

//No need to edit beyond here...

jQuery.noConflict()

function fadeSlideShow(settingarg){
	this.setting=settingarg
	settingarg=null
	var setting=this.setting
	setting.fadeduration=setting.fadeduration? parseInt(setting.fadeduration) : 500
	setting.curimage=(setting.persist)? fadeSlideShow.routines.getCookie("gallery-"+setting.wrapperid) : 0
	setting.curimage=setting.curimage || 0 //account for curimage being null if cookie is empty
	setting.currentstep=0 //keep track of # of slides slideshow has gone through (applicable in displaymode='auto' only)
	setting.totalsteps=setting.imagearray.length*(setting.displaymode.cycles>0? setting.displaymode.cycles : Infinity) //Total steps limit (applicable in displaymode='auto' only w/ cycles>0)
	setting.fglayer=0, setting.bglayer=1 //index of active and background layer (switches after each change of slide)
	setting.oninit=setting.oninit || function(){}
	setting.onslide=setting.onslide || function(){}
	if (setting.displaymode.randomize) //randomly shuffle order of images?
		setting.imagearray.sort(function() {return 0.5 - Math.random()})
	var preloadimages=[] //preload images
	setting.longestdesc="" //get longest description of all slides. If no desciptions defined, variable contains ""
	for (var i=0; i<setting.imagearray.length; i++){ //preload images
		preloadimages[i]=new Image()
		preloadimages[i].src=setting.imagearray[i][0]
		if (setting.imagearray[i][3] && setting.imagearray[i][3].length>setting.longestdesc.length)
			setting.longestdesc=setting.imagearray[i][3]
	}
	var closebutt=fadeSlideShow_descpanel.controls[0] //add close button to "desc" panel if descreveal="always"
	setting.closebutton=(setting.descreveal=="always")? '<img class="close" src="'+closebutt[0]+'" style="float:right;cursor:hand;cursor:pointer;width:'+closebutt[1]+'px;height:'+closebutt[2]+'px;margin-left:2px" title="Hide Description" />' : ''
	var slideshow=this
	jQuery(document).ready(function($){ //fire on DOM ready
		var setting=slideshow.setting
		var fullhtml=fadeSlideShow.routines.getFullHTML(setting.imagearray) //get full HTML of entire slideshow
		setting.$wrapperdiv=$('#'+setting.wrapperid).css({position:'relative', visibility:'visible', background:'black', overflow:'hidden', width:setting.dimensions[0], height:setting.dimensions[1]}).empty() //main slideshow DIV
		if (setting.$wrapperdiv.length==0){ //if no wrapper DIV found
			alert("Error: DIV with ID \""+setting.wrapperid+"\" not found on page.")
			return
		}
		setting.$gallerylayers=$('<div class="gallerylayer"></div><div class="gallerylayer"></div>') //two stacked DIVs to display the actual slide 
			.css({position:'absolute', left:0, top:0, width:'100%', height:'100%', background:'black'})
			.appendTo(setting.$wrapperdiv)
		var $loadingimg=$('<img src="'+fadeSlideShow_descpanel.controls[2][0]+'" style="position:absolute;width:'+fadeSlideShow_descpanel.controls[2][1]+';height:'+fadeSlideShow_descpanel.controls[2][2]+'" />')
			.css({left:setting.dimensions[0]/2-fadeSlideShow_descpanel.controls[2][1]/2, top:setting.dimensions[1]/2-fadeSlideShow_descpanel.controls[2][2]}) //center loading gif
			.appendTo(setting.$wrapperdiv)
		var $curimage=setting.$gallerylayers.html(fullhtml).find('img').hide().eq(setting.curimage) //prefill both layers with entire slideshow content, hide all images, and return current image
		if (setting.longestdesc!="" && setting.descreveal!="none"){ //if at least one slide contains a description (versus feature is enabled but no descriptions defined) and descreveal not explicitly disabled
			fadeSlideShow.routines.adddescpanel($, setting)
			if (setting.descreveal=="always"){ //position desc panel so it's visible to begin with
				setting.$descpanel.css({top:setting.dimensions[1]-setting.panelheight})
				setting.$descinner.click(function(e){ //asign click behavior to "close" icon
					if (e.target.className=="close"){
						slideshow.showhidedescpanel('hide')
					}
				})
				setting.$restorebutton.click(function(e){ //asign click behavior to "restore" icon
					slideshow.showhidedescpanel('show')
					$(this).css({visibility:'hidden'})
				})
			}
			else if (setting.descreveal=="ondemand"){ //display desc panel on demand (mouseover)
				setting.$wrapperdiv.bind('mouseenter', function(){slideshow.showhidedescpanel('show')})
				setting.$wrapperdiv.bind('mouseleave', function(){slideshow.showhidedescpanel('hide')})
			}
		}
		setting.$wrapperdiv.bind('mouseenter', function(){setting.ismouseover=true}) //pause slideshow mouseover
		setting.$wrapperdiv.bind('mouseleave', function(){setting.ismouseover=false})
		if ($curimage.get(0).complete){ //accounf for IE not firing image.onload
			$loadingimg.hide()
			slideshow.paginateinit($)
			slideshow.showslide(setting.curimage)
		}
		else{ //initialize slideshow when first image has fully loaded
			$loadingimg.hide()
			slideshow.paginateinit($)
			$curimage.bind('load', function(){slideshow.showslide(setting.curimage)})
		}
		setting.oninit.call(slideshow) //trigger oninit() event
		$(window).bind('unload', function(){ //clean up and persist
			if (slideshow.setting.persist) //remember last shown image's index
				fadeSlideShow.routines.setCookie("gallery-"+setting.wrapperid, setting.curimage)
			jQuery.each(slideshow.setting, function(k){
				if (slideshow.setting[k] instanceof Array){
					for (var i=0; i<slideshow.setting[k].length; i++){
						if (slideshow.setting[k][i].tagName=="DIV") //catches 2 gallerylayer divs, gallerystatus div
							slideshow.setting[k][i].innerHTML=null
						slideshow.setting[k][i]=null
					}
				}
			})
			slideshow=slideshow.setting=null
		})
	})
}

fadeSlideShow.prototype={

	navigate:function(keyword){
		var setting=this.setting
		clearTimeout(setting.playtimer)
		if (setting.displaymode.type=="auto"){ //in auto mode
			setting.displaymode.type="manual" //switch to "manual" mode when nav buttons are clicked on
			setting.displaymode.wraparound=true //set wraparound option to true
		}
		if (!isNaN(parseInt(keyword))){ //go to specific slide?
			this.showslide(parseInt(keyword))
		}
		else if (/(prev)|(next)/i.test(keyword)){ //go back or forth inside slide?
			this.showslide(keyword.toLowerCase())
		}
	},

	showslide:function(keyword){
		var slideshow=this
		var setting=slideshow.setting
		if (setting.displaymode.type=="auto" && setting.ismouseover && setting.currentstep<=setting.totalsteps){ //if slideshow in autoplay mode and mouse is over it, pause it
			setting.playtimer=setTimeout(function(){slideshow.showslide('next')}, setting.displaymode.pause)
			return
		}
		var totalimages=setting.imagearray.length
		var imgindex=(keyword=="next")? (setting.curimage<totalimages-1? setting.curimage+1 : 0)
			: (keyword=="prev")? (setting.curimage>0? setting.curimage-1 : totalimages-1)
			: Math.min(keyword, totalimages-1)
		var $slideimage=setting.$gallerylayers.eq(setting.bglayer).find('img').hide().eq(imgindex).show() //hide all images except current one
		var imgdimensions=[$slideimage.width(), $slideimage.height()] //center align image
		$slideimage.css({marginLeft: (imgdimensions[0]>0 && imgdimensions[0]<setting.dimensions[0])? setting.dimensions[0]/2-imgdimensions[0]/2 : 0})
		$slideimage.css({marginTop: (imgdimensions[1]>0 && imgdimensions[1]<setting.dimensions[1])? setting.dimensions[1]/2-imgdimensions[1]/2 : 0})
		if (setting.descreveal=="peekaboo" && setting.longestdesc!=""){ //if descreveal is set to "peekaboo", make sure description panel is hidden before next slide is shown
			clearTimeout(setting.hidedesctimer) //clear hide desc panel timer
			slideshow.showhidedescpanel('hide', 0) //and hide it immediately
		}
		setting.$gallerylayers.eq(setting.bglayer).css({zIndex:1000, opacity:0}) //background layer becomes foreground
			.stop().css({opacity:0}).animate({opacity:1}, setting.fadeduration, function(){ //Callback function after fade animation is complete:
				clearTimeout(setting.playtimer)
				try{
					setting.onslide.call(slideshow, setting.$gallerylayers.eq(setting.fglayer).get(0), setting.curimage)
				}catch(e){
					alert("Fade In Slideshow error: An error has occured somwhere in your code attached to the \"onslide\" event: "+e)
				}
				if (setting.descreveal=="peekaboo" && setting.longestdesc!=""){
					slideshow.showhidedescpanel('show')
					setting.hidedesctimer=setTimeout(function(){slideshow.showhidedescpanel('hide')}, setting.displaymode.pause-fadeSlideShow_descpanel.slidespeed)
				}	
				setting.currentstep+=1
				if (setting.displaymode.type=="auto"){
					if (setting.currentstep<=setting.totalsteps || setting.displaymode.cycles==0)
						setting.playtimer=setTimeout(function(){slideshow.showslide('next')}, setting.displaymode.pause)
				}
			}) //end callback function
		setting.$gallerylayers.eq(setting.fglayer).css({zIndex:999}) //foreground layer becomes background
		setting.fglayer=setting.bglayer
		setting.bglayer=(setting.bglayer==0)? 1 : 0
		setting.curimage=imgindex
		if (setting.$descpanel){
			setting.$descpanel.css({visibility:(setting.imagearray[imgindex][3])? 'visible' : 'hidden'})
			if (setting.imagearray[imgindex][3]) //if this slide contains a description
				setting.$descinner.empty().html(setting.closebutton + setting.imagearray[imgindex][3])
		}
		if (setting.displaymode.type=="manual" && !setting.displaymode.wraparound){
			this.paginatecontrol()
		}
		if (setting.$status) //if status container defined
			setting.$status.html(setting.curimage+1 + "/" + totalimages)
	},

	showhidedescpanel:function(state, animateduration){
		var setting=this.setting
		var endpoint=(state=="show")? setting.dimensions[1]-setting.panelheight : this.setting.dimensions[1]
		setting.$descpanel.stop().animate({top:endpoint}, (typeof animateduration!="undefined"? animateduration : fadeSlideShow_descpanel.slidespeed), function(){
			if (setting.descreveal=="always" && state=="hide")
				setting.$restorebutton.css({visibility:'visible'}) //show restore button
		})
	},

	paginateinit:function($){
		var slideshow=this
		var setting=this.setting
		if (setting.togglerid){ //if toggler div defined
			setting.$togglerdiv=$("#"+setting.togglerid)
			setting.$prev=setting.$togglerdiv.find('.prev').data('action', 'prev')
			setting.$next=setting.$togglerdiv.find('.next').data('action', 'next')
			setting.$prev.add(setting.$next).click(function(e){ //assign click behavior to prev and next controls
				var $target=$(this)
				slideshow.navigate($target.data('action'))
				e.preventDefault()
			})
			setting.$status=setting.$togglerdiv.find('.status')
		}
	},

	paginatecontrol:function(){
		var setting=this.setting
			setting.$prev.css({opacity:(setting.curimage==0)? 0.4 : 1}).data('action', (setting.curimage==0)? 'none' : 'prev')
			setting.$next.css({opacity:(setting.curimage==setting.imagearray.length-1)? 0.4 : 1}).data('action', (setting.curimage==setting.imagearray.length-1)? 'none' : 'next')
			if (document.documentMode==8){ //in IE8 standards mode, apply opacity to inner image of link
				setting.$prev.find('img:eq(0)').css({opacity:(setting.curimage==0)? 0.4 : 1})
				setting.$next.find('img:eq(0)').css({opacity:(setting.curimage==setting.imagearray.length-1)? 0.4 : 1})
			}
	}

	
}

fadeSlideShow.routines={

	getSlideHTML:function(imgelement){
		var layerHTML=(imgelement[1])? '<a href="'+imgelement[1]+'" target="'+imgelement[2]+'">\n' : '' //hyperlink slide?
		layerHTML+='<img src="'+imgelement[0]+'" style="border-width:0;" />\n'
		layerHTML+=(imgelement[1])? '</a>\n' : ''
		return layerHTML //return HTML for this layer
	},

	getFullHTML:function(imagearray){
		var preloadhtml=''
		for (var i=0; i<imagearray.length; i++)
			preloadhtml+=this.getSlideHTML(imagearray[i])
		return preloadhtml
	},

	adddescpanel:function($, setting){
		setting.$descpanel=$('<div class="fadeslidedescdiv"></div>')
			.css({position:'absolute', visibility:'hidden', width:'100%', left:0, top:setting.dimensions[1], font:fadeSlideShow_descpanel.fontStyle, zIndex:'1001'})
			.appendTo(setting.$wrapperdiv)
		$('<div class="descpanelbg"></div><div class="descpanelfg"></div>') //create inner nav panel DIVs
			.css({position:'absolute', left:0, top:0, width:setting.$descpanel.width()-8, padding:'4px'})
			.eq(0).css({background:'black', opacity:0.7}).end() //"descpanelbg" div
			.eq(1).css({color:'white'}).html(setting.closebutton + setting.longestdesc).end() //"descpanelfg" div
			.appendTo(setting.$descpanel)
		setting.$descinner=setting.$descpanel.find('div.descpanelfg')
		setting.panelheight=setting.$descinner.outerHeight()
		setting.$descpanel.css({height:setting.panelheight}).find('div').css({height:'100%'})
		if (setting.descreveal=="always"){ //create restore button
			setting.$restorebutton=$('<img class="restore" title="Restore Description" src="' + fadeSlideShow_descpanel.controls[1][0] +'" style="position:absolute;visibility:hidden;right:0;bottom:0;z-index:1002;width:'+fadeSlideShow_descpanel.controls[1][1]+'px;height:'+fadeSlideShow_descpanel.controls[1][2]+'px;cursor:pointer;cursor:hand" />')
				.appendTo(setting.$wrapperdiv)


		}
	},


	getCookie:function(Name){ 
		var re=new RegExp(Name+"=[^;]+", "i"); //construct RE to search for target name/value pair
		if (document.cookie.match(re)) //if cookie found
			return document.cookie.match(re)[0].split("=")[1] //return its value
		return null
	},

	setCookie:function(name, value){
		document.cookie = name+"=" + value + ";path=/"
	}
}
