PageSourceSearch

https://www.wcdrr.org/assets/js/MetroJs.js

js wcdrr.org collected 2026-10-02 03:42:36 UTC 116,513 bytes, 2,697 lines download raw bytes

1/*!
2* Metro JS for jQuery
3* http://drewgreenwell.com/ 
4* For details and usage info see: http://drewgreenwell.com/projects/metrojs
5
6Copyright (C) 2013, Drew Greenwell
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 
9to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
10and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
15FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
16WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17*/
18;(function ($) {
19    // the metrojs object contains helper methods and theme settings
20    $.fn.metrojs = {
21        capabilities: null,
22        checkCapabilities: function(stgs, recheck){
23            if($.fn.metrojs.capabilities == null || (typeof(recheck) != "undefined" && recheck == true))
24                $.fn.metrojs.capabilities = new $.fn.metrojs.MetroModernizr(stgs);
25            return  $.fn.metrojs.capabilities;
26        }
27    };
28	var metrojs = $.fn.metrojs,
29		console = window.console;	
30	if (typeof console !== "object") {
31		console = {};
32		console.log = function() {};
33		console.error=function() {};
34	}
35	var throwError = typeof ($.error) === "function" ? $.error : console.error;
36var MAX_LOOP_COUNT = 99000;
37// .liveTile
38$.fn.liveTile = function (method) {
39	if (pubMethods[method]) {
40		var args = [];
41		for (var i = 1; i <= arguments.length; i++) {
42			args[i - 1] = arguments[i];
43		}
44		return pubMethods[method].apply(this, args);
45	} else if (typeof method === 'object' || !method) {
46		return pubMethods.init.apply(this, arguments);
47	} else {
48		$.error('Method ' + method + ' does not exist on jQuery.liveTile');
49		return null;
50	}
51};
52
53
54$.fn.liveTile.contentModules = {
55	modules: [],
56	/* the default module layout
57    [
58        defaultModules.imageSwap,
59        defaultModules.htmlSwap
60    ],*/
61	addContentModule: function (moduleName, module) {
62		if (!(this.modules instanceof Array))
63			this.modules = [];
64		this.modules.push(module);
65	},
66	hasContentModule: function (moduleName) {
67		if (typeof (moduleName) === "undefined" || !(this.modules instanceof Array))
68			return -1;
69		for (var i = 0; i < this.modules.length; i++) {
70			if (typeof (this.modules[i].moduleName) != "undefined" && this.modules[i].moduleName == moduleName)
71				return i;
72		}
73		return -1;
74	}
75};
76
77// default option values for .liveTile
78$.fn.liveTile.defaults = {
79	mode: 'slide',                          // 'fade', 'slide', 'flip', 'flip-list', carousel
80	speed: 500,                             // how fast should animations be performed, in milliseconds
81	initDelay: -1,                          // how long to wait before the initial animation
82	delay: 5000,                            // how long to wait between animations 
83	stops: "100%",                          // how much of the back tile should 'slide' reveal before starting a delay
84	stack: false,                           // should tiles in slide mode appear stacked (e.g Me tile) 
85	direction: 'vertical',                  // which direction should animations be performed(horizontal | vertical)
86	animationDirection: 'forward',          // the direction that carousel mode uses to determine which way to slide in tiles
87	tileSelector: '>div,>li,>p,>img,>a',    // the selector used by carousel mode and flip-list to choose tile containers
88	tileFaceSelector: '>div,>li,>p,>img,>a',// the selector used to choose the front and back containers
89	ignoreDataAttributes: false,            // should data attributes be ignored
90	click: null,                            // function ($tile, tdata) { return true; }
91	link: '',                               // a url to go to when clicked
92	newWindow: false,                       // should the link be opened in a new window
93	bounce: false,                          // should the tile shrink when tapped
94	bounceDirections: 'all',                // which direction the tile will tile 'all', 'edges, 'corners'
95	bounceFollowsMove: true,                // should a tile in bounce state tilt in the direction of the mouse as it moves
96	pauseOnHover: false,                    // should tile animations be paused on hover in and restarted on hover out
97	pauseOnHoverEvent: 'both',              // pause is called on mouseover, mouseout, or both
98	playOnHover: false,                     // should "play" be called on hover
99	playOnHoverEvent: 'both',               // play is called on mouseover, mouseout, or both
100	onHoverDelay: 0,						// the amount of time to wait before the onHover event is fired
101	onHoverOutDelay: 200,					// the amount of time in addition to the speed to wait before the onHoverOut event is fired
102	repeatCount: -1,                        // number of times to repeat the animation        
103	appendBack: true,                       // appends the .last tile if one doesnt exist (slide and flip only)        
104	alwaysTrigger: false,                   // should every item in a flip list trigger every time a delay passes 
105	flipListOnHover: false,                 // should items in flip-list flip and stop when hovered
106	flipListOnHoverEvent: 'mouseout',       // which event should be used to trigger the flip-list faces
107	noHAflipOpacity: '1',                   // the opacity level set for the backside of the flip animation on unaccelerated browsers
108	haTransFunc: 'ease',                    // the tranisiton-timing function to use in hardware accelerated mode
109	noHaTransFunc: 'linear',                // the tranisiton-timing function to use in non hardware accelerated mode
110	currentIndex: 0,                        // what is the current stop index for slide mode or slide index for carousel mode
111	startNow: true,                         // should the tile immediately start or wait util play or restart has been called
112	useModernizr: (typeof (window.Modernizr) !== "undefined"), // checks to see if modernizer is already in use
113	useHardwareAccel: true,                 // should css animations, transitions and transforms be used when available
114	useTranslate: true,
115	faces: {
116		$front: null,                        // the jQuery element to use as the front face of the tile; this will bypass tileCssSelector
117		$back: null                          // the jQuery element to use as the back face of the tile; this will bypass tileCssSelector
118	},
119	animationStarting: function (tileData, $front, $back) {
120		// returning false will cancel the animation
121	},
122	animationComplete: function (tileData, $front, $back) {
123	},
124	triggerDelay: function (idx) {          // used by flip-list to decide how random the tile flipping should be
125		return Math.random() * 3000;
126	},
127	swap: '',                               // which swap modules are active for this tile (image, html)
128	swapFront: '-',                         // override the available swap modules for the front face
129	swapBack: '-',                          // override the available swap modules for the back face
130	contentModules: [],
131	rebindMessage: "tile data is missing. Are you missing a call to rebind or destroy? You may also be able to avoid this error by calling stop or pause"
132};
133// public methods that can be called via .liveTile(method name)
134var pubMethods = {
135	init: function (options) {
136		// Setup the public options for the livetile
137		var settings = $.extend({}, $.fn.liveTile.defaults, options);
138		// checks for browser feature support to enable hardware acceleration                        
139		metrojs.checkCapabilities(settings);
140		helperMethods.getBrowserPrefix();
141		// setup the default content modules
142		if ($.fn.liveTile.contentModules.hasContentModule("image") == -1)
143			$.fn.liveTile.contentModules.addContentModule("image", defaultModules.imageSwap);
144		if ($.fn.liveTile.contentModules.hasContentModule("html") == -1)
145			$.fn.liveTile.contentModules.addContentModule("html", defaultModules.htmlSwap);
146		// this is where the magic happens
147		return $(this).each(function (tileIndex, ele) {
148			var $this = $(ele),
149            data = privMethods.initTileData($this, settings);
150			// append back tiles and add appropriate classes to prepare tiles
151			data.faces = privMethods.prepTile($this, data);
152			// action methods
153			data.fade = function (count) { privMethods.fade($this, count); };
154			data.slide = function (count) { privMethods.slide($this, count); };
155			data.carousel = function (count) { privMethods.carousel($this, count); };
156			data.flip = function (count) { privMethods.flip($this, count); };
157			data.flipList = function (count) { privMethods.flipList($this, count); };
158			var actions = {
159				fade: data.fade,
160				slide: data.slide,
161				carousel: data.carousel,
162				flip: data.flip,
163				'flip-list': data.flipList
164			};
165			data.doAction = function (count) {
166				// get the action for the current mode
167				var action = actions[data.mode];
168				if (typeof (action) === "function") {
169					action(count);
170					data.hasRun = true;
171				}
172				// prevent pauseOnHover from resuming a tile that has finished
173				if (count == data.timer.repeatCount)
174					data.runEvents = false;
175			};
176
177			// create a new tile timer
178			data.timer = new $.fn.metrojs.TileTimer(data.delay, data.doAction, data.repeatCount);
179			// apply the data
180			$this.data("LiveTile", data);
181			// handle events
182			// only bind pause / play on hover if we are not using a fliplist or flipListOnHover isn't set set
183			if (data.mode !== "flip-list" || data.flipListOnHover == false) {
184				if (data.pauseOnHover) {
185					privMethods.bindPauseOnHover($this);
186				} else if (data.playOnHover) {
187					privMethods.bindPlayOnHover($this, data);
188				}
189			}
190			// add a click / link to the tile
191			if (data.link.length > 0 || typeof (data.click) === "function") {
192				$this.css({ cursor: 'pointer' }).bind("click.liveTile", function (e) {
193					var proceed = true;
194					if (typeof (data.click) === "function") {
195						proceed = data.click($this, data) || false;
196					}
197					if (proceed && data.link.length > 0) {
198						e.preventDefault();
199						if (data.newWindow)
200							window.open(data.link);
201						else
202							window.location = data.link;
203					}
204				});
205			}
206			// add bounce if set            
207			privMethods.bindBounce($this, data);
208			// start timer
209			if (data.startNow && data.mode != "none") {
210				data.runEvents = true;
211				data.timer.start(data.initDelay);
212			}
213		});
214	},
215	// goto is a future reserved word
216	'goto': function (options) {
217		var opts, t = typeof (options);
218		if (t === "undefined") {
219			opts = {
220				index: -99, //  same as next
221				delay: 0,
222				autoAniDirection: false
223			};
224		}
225		if (t === "number" || !isNaN(options)) {
226			opts = {
227				index: parseInt(options, 10),
228				delay: 0
229			};
230		} else if (t === "string") {
231			if (options == "next") {
232				opts = {
233					index: -99,
234					delay: 0
235				};
236			} else if (options.indexOf("prev") === 0) {
237				opts = {
238					index: -100,
239					delay: 0
240				};
241			} else {
242				$.error(options + " is not a recognized action for .liveTile(\"goto\")");
243				return $(this);
244			}
245		} else if (t === "object") {
246			if (typeof (options.delay) === "undefined") {
247				options.delay = 0;
248			}
249			var ti = typeof (options.index);
250			if (ti === "undefined") {
251				options.index = 0;
252			} else if (ti === "string") {
253				if (options.index === "next")
254					options.index = -99;
255				else if (options.index.indexOf("prev") === 0)
256					options.index = -100;
257			}
258			opts = options;
259		}
260		return $(this).each(function (tileIndex, ele) {
261			var $tile = $(ele),
262                data = $tile.data("LiveTile"),
263                aniData = $tile.data("metrojs.tile"),
264                goTo = opts.index;
265			if (aniData.animating === true)
266				return $(this);
267			if (data.mode === "carousel") {
268				// get the index based off of the active carousel slide
269				var $cur = data.faces.$listTiles.filter(".active");
270				var curIdx = data.faces.$listTiles.index($cur);
271				// carousel will look for these values as triggers
272				if (goTo === -100) { // prev
273					// autoAniDirection determines if a forward or backward animation should be used based on the goTo index
274					if (typeof (opts.autoAniDirection) === "undefined" || opts.autoAniDirection == true)
275						data.tempValues.animationDirection = typeof (opts.animationDirection) === "undefined" ? "backward" : opts.animationDirection;
276					goTo = curIdx === 0 ? data.faces.$listTiles.length - 1 : curIdx - 1;
277				} else if (goTo === -99) { // next
278					if (typeof (opts.autoAniDirection) === "undefined" || opts.autoAniDirection == true)
279						data.tempValues.animationDirection = typeof (opts.animationDirection) === "undefined" ? "forward" : opts.animationDirection;
280					goTo = curIdx + 1;
281				}
282				if (curIdx == goTo) {
283					return;
284				}
285				if (typeof (opts.direction) !== "undefined") {
286					data.tempValues.direction = opts.direction;
287				}
288				if (typeof (opts.animationDirection) !== "undefined") {
289					data.tempValues.animationDirection = opts.animationDirection;
290				}
291				// the index is offset by 1 and incremented on animate
292				if (goTo == 0)
293					data.currentIndex = data.faces.$listTiles.length;
294				else
295					data.currentIndex = goTo - 1;
296			} else // slide mode will use the index directly
297				data.currentIndex = goTo;
298			// start the timer
299			data.runEvents = true;
300			data.timer.start(opts.delay >= 0 ? opts.delay : data.delay);
301		});
302	},
303	play: function (options) {
304		var opts, t = typeof (options);
305		if (t === "undefined") {
306			opts = {
307				delay: -1
308			};
309		} else if (t === "number") {
310			opts = {
311				delay: options
312			};
313		} else if (t === "object") {
314			if (typeof (options.delay) === "undefined") {
315				options.delay = -1;
316			}
317			opts = options;
318		}
319		return $(this).each(function (tileIndex, ele) {
320			var $tile = $(ele),
321                data = $tile.data("LiveTile");
322			data.runEvents = true;
323			if (opts.delay < 0 && !data.hasRun)
324				opts.delay = data.initDelay;
325			data.timer.start(opts.delay >= 0 ? opts.delay : data.delay);
326		});
327	},
328	animate: function () { // this is really only useful for certain edge cases in slide mode, use 'play' to toggle animations
329		return $(this).each(function (tileIndex, ele) {
330			var $tile = $(ele),
331                data = $tile.data("LiveTile");
332			data.doAction();
333		});
334	},
335	stop: function () {
336		return $(this).each(function (tileIndex, ele) {
337			var $tile = $(ele),
338                data = $tile.data("LiveTile");
339			data.hasRun = false;
340			data.runEvents = false;
341			data.timer.stop();
342			window.clearTimeout(data.eventTimeout);
343			window.clearTimeout(data.flCompleteTimeout);
344			window.clearTimeout(data.completeTimeout);
345			if (data.mode === "flip-list") {
346				data.faces.$listTiles.each(function (idx, li) {
347					var ldata = $(li).data("metrojs.tile");
348					window.clearTimeout(ldata.eventTimeout);
349					window.clearTimeout(ldata.flCompleteTimeout);
350					window.clearTimeout(ldata.completeTimeout);
351				});
352			}
353		});
354	},
355	pause: function () {
356		return $(this).each(function (tileIndex, ele) {
357			var $tile = $(ele),
358                data = $tile.data("LiveTile");
359			data.timer.pause();
360			data.runEvents = false;
361			window.clearTimeout(data.eventTimeout);
362			window.clearTimeout(data.flCompleteTimeout);
363			window.clearTimeout(data.completeTimeout);
364			if (data.mode === "flip-list") {
365				data.faces.$listTiles.each(function (idx, li) {
366					var ldata = $(li).data("metrojs.tile");
367					window.clearTimeout(ldata.eventTimeout);
368					window.clearTimeout(ldata.flCompleteTimeout);
369					window.clearTimeout(ldata.completeTimeout);
370				});
371			}
372		});
373	},
374	restart: function (options) {
375		var opts, t = typeof (options);
376		if (t === "undefined") {
377			opts = {
378				delay: -1
379			};
380		} else if (t === "number") {
381			opts = {
382				delay: options
383			};
384		} else if (t === "object") {
385			if (typeof (options.delay) === "undefined") {
386				options.delay = -1;
387			}
388			opts = options;
389		}
390		return $(this).each(function (tileIndex, ele) {
391			var $tile = $(ele),
392                data = $tile.data("LiveTile");
393			if (opts.delay < 0 && !data.hasRun)
394				opts.delay = data.initDelay;
395			data.hasRun = false;
396			data.runEvents = true;
397			data.timer.restart(opts.delay >= 0 ? opts.delay : data.delay);
398		});
399	},
400	rebind: function (options) {
401		return $(this).each(function (tileIndex, ele) {
402			if (typeof (options) !== "undefined") {
403				if (typeof (options.timer) !== "undefined" && options.timer != null) {
404					options.timer.stop();
405				}
406				options.hasRun = false;
407				pubMethods["init"].apply(ele, [options]);
408			} else {
409				pubMethods["init"].apply(ele, [{}]);
410			}
411		});
412	},
413	destroy: function (options) {
414		var t = typeof (options), opts;
415		if (t === "undefined") {
416			opts = {
417				removeCss: false
418			};
419		} else if (t === "boolean") {
420			opts = {
421				removeCss: options
422			};
423		} else if (t === "object") {
424			if (typeof (options.removeCss) === "undefined") {
425				options.removeCss = false;
426			}
427			opts = options;
428		}
429		return $(this).each(function (tileIndex, ele) {
430			var $tile = $(ele);
431			var data = $tile.data("LiveTile");
432			if (typeof (data) === "undefined")
433				return;
434			$tile.unbind(".liveTile");
435			var resetCss = helperMethods.appendStyleProperties({ margin: '', cursor: '' }, ['transform', 'transition'], ['', '']);
436			data.timer.stop();
437			window.clearTimeout(data.eventTimeout);
438			window.clearTimeout(data.flCompleteTimeout);
439			window.clearTimeout(data.completeTimeout);
440			if (data.faces.$listTiles != null) {
441				data.faces.$listTiles.each(function (idx, li) {
442					var $li = $(li);
443					if (data.mode === "flip-list") {
444						var ldata = $li.data("metrojs.tile");
445						window.clearTimeout(ldata.eventTimeout);
446						window.clearTimeout(ldata.flCompleteTimeout);
447						window.clearTimeout(ldata.completeTimeout);
448					} else if (data.mode === "carousel") {
449						var sdata = data.listData[idx];
450						if (sdata.bounce) {
451							privMethods.unbindMsBounce($li, sdata);
452						}
453					}
454					if (opts.removeCss) {
455						$li.removeClass("ha");
456						$li.find(data.tileFaceSelector)
457                            .unbind(".liveTile")
458                            .removeClass("bounce flip-front flip-back ha slide slide-front slide-back")
459                            .css(resetCss);
460					} else {
461						$li.find(data.tileFaceSelector).unbind(".liveTile");
462					}
463					$li.removeData("metrojs.tile");
464				}).unbind(".liveTile");
465			}
466			if (data.faces.$front != null && opts.removeCss) {
467				data.faces.$front.removeClass("flip-front flip-back ha slide slide-front slide-back")
468                    .css(resetCss);
469			}
470			if (data.faces.$back != null && opts.removeCss) {
471				data.faces.$back.removeClass("flip-front flip-back ha slide slide-front slide-back")
472                    .css(resetCss);
473			}
474			// remove the bounce and hover methods
475			// todo: combine all mouse/touch events (down, move, up)
476			if (data.bounce) {
477				privMethods.unbindMsBounce($tile, data);
478			}
479			if (data.playOnHover) {
480				privMethods.unbindMsPlayOnHover($tile, data);
481			}
482			if (data.pauseOnhover) {
483				privMethods.unbindMsPauseOnHover($tile, data);
484			}
485			$tile.removeClass("ha");
486			$tile.removeData("LiveTile");
487			$tile.removeData("metrojs.tile");
488			data = null;
489		});
490	}
491};
492
493// private methods that are called by .liveTile
494var privMethods = {
495	//getDataOrDefault for older versions of jQuery that dont look for 'data-' properties
496	dataAtr: function ($ele, name, def) {
497		return typeof ($ele.attr('data-' + name)) !== "undefined" ? $ele.attr('data-' + name) : def;
498	},
499	dataMethod: function ($ele, name, def) {
500		return typeof ($ele.data(name)) !== "undefined" ? $ele.data(name) : def;
501	},
502	getDataOrDefault: null,
503	initTileData: function ($tile, stgs) {
504		var useData = stgs.ignoreDataAttributes == false,
505            tdata = null;
506		if (this.getDataOrDefault == null)
507			this.getDataOrDefault = metrojs.capabilities.isOldJQuery ? this.dataAtr : this.dataMethod;
508		if (useData) {
509			tdata = { //an object to store settings for later access                
510				speed: this.getDataOrDefault($tile, "speed", stgs.speed),
511				delay: this.getDataOrDefault($tile, "delay", stgs.delay),
512				stops: this.getDataOrDefault($tile, "stops", stgs.stops),
513				stack: this.getDataOrDefault($tile, "stack", stgs.stack),
514				mode: this.getDataOrDefault($tile, "mode", stgs.mode),
515				direction: this.getDataOrDefault($tile, "direction", stgs.direction),
516				useHardwareAccel: this.getDataOrDefault($tile, "ha", stgs.useHardwareAccel),
517				repeatCount: this.getDataOrDefault($tile, "repeat", stgs.repeatCount),
518				swap: this.getDataOrDefault($tile, "swap", stgs.swap),
519				appendBack: this.getDataOrDefault($tile, "appendback", stgs.appendBack),
520				currentIndex: this.getDataOrDefault($tile, "start-index", stgs.currentIndex),
521				animationDirection: this.getDataOrDefault($tile, "ani-direction", stgs.animationDirection),
522				startNow: this.getDataOrDefault($tile, "start-now", stgs.startNow),
523				tileSelector: this.getDataOrDefault($tile, "tile-selector", stgs.tileSelector),
524				tileFaceSelector: this.getDataOrDefault($tile, "face-selector", stgs.tileFaceSelector),
525				bounce: this.getDataOrDefault($tile, "bounce", stgs.bounce),
526				bounceDirections: this.getDataOrDefault($tile, "bounce-dir", stgs.bounceDirections),
527				bounceFollowsMove: this.getDataOrDefault($tile, "bounce-follows", stgs.bounceFollowsMove),
528				click: this.getDataOrDefault($tile, "click", stgs.click),
529				link: this.getDataOrDefault($tile, "link", stgs.link),
530				newWindow: this.getDataOrDefault($tile, "new-window", stgs.newWindow),
531				alwaysTrigger: this.getDataOrDefault($tile, "always-trigger", stgs.alwaysTrigger),
532				flipListOnHover: this.getDataOrDefault($tile, "flip-onhover", stgs.flipListOnHover),
533				pauseOnHover: this.getDataOrDefault($tile, "pause-onhover", stgs.pauseOnHover),
534				playOnHover: this.getDataOrDefault($tile, "play-onhover", stgs.playOnHover),
535				onHoverDelay: this.getDataOrDefault($tile, "hover-delay", stgs.onHoverDelay),
536				onHoverOutDelay: this.getDataOrDefault($tile, "hoverout-delay", stgs.onHoverOutDelay),
537				noHAflipOpacity: this.getDataOrDefault($tile, "flip-opacity", stgs.noHAflipOpacity),
538				useTranslate: this.getDataOrDefault($tile, "use-translate", stgs.useTranslate),
539				runEvents: false,
540				isReversed: false,
541				loopCount: 0,
542				contentModules: [],
543				listData: [],
544				height: $tile.height(),
545				width: $tile.width(),
546				tempValues: {}
547			};
548		} else {
549			tdata = $.extend(true, {
550				runEvents: false,
551				isReversed: false,
552				loopCount: 0,
553				contentModules: [],
554				listData: [],
555				height: $tile.height(),
556				width: $tile.width(),
557				tempValues: {}
558			}, stgs);
559		}
560		tdata.useTranslate = tdata.useTranslate && tdata.useHardwareAccel && metrojs.capabilities.canTransform && metrojs.capabilities.canTransition;
561		// set the margin to half of the height or width based on the direction
562		tdata.margin = (tdata.direction === "vertical") ? tdata.height / 2 : tdata.width / 2;
563		// convert stops if needed
564		tdata.stops = (typeof (stgs.stops) === "object" && (stgs.stops instanceof Array)) ? stgs.stops : ("" + tdata.stops).split(",");
565		// add a return stop
566		if (tdata.stops.length === 1)
567			tdata.stops.push("0px");
568		// add content modules, start with global swaps            
569		var swaps = tdata.swap instanceof Array ? tdata.swap : tdata.swap.replace(' ', '').split(",");
570		// get the front and back swap data
571		var sf = useData ? this.getDataOrDefault($tile, "swap-front", stgs.swapFront) : stgs.swapFront;
572		var sb = useData ? this.getDataOrDefault($tile, "swap-back", stgs.swapBack) : stgs.swapBack;
573		// set the data to the global value if its still the default
574		if (sf instanceof Array) {
575			tdata.swapFront = sf;
576		} else {
577			tdata.swapFront = sf === '-' ? swaps : sf.replace(' ', '').split(",");
578		}
579
580		if (sb instanceof Array) {
581			tdata.swapBack = sb;
582		} else {
583			tdata.swapBack = sb === '-' ? swaps : sb.replace(' ', '').split(",");
584		}
585		// make sure the swaps includes all front and back swaps
586		var i;
587		for (i = 0; i < tdata.swapFront.length; i++) {
588			if (tdata.swapFront[i].length > 0 && $.inArray(tdata.swapFront[i], swaps) === -1)
589				swaps.push(tdata.swapFront[i]);
590		}
591		for (i = 0; i < tdata.swapBack.length; i++) {
592			if (tdata.swapBack[i].length > 0 && $.inArray(tdata.swapBack[i], swaps) === -1)
593				swaps.push(tdata.swapBack[i]);
594		}
595		tdata.swap = swaps;
596		// add all required content modules for the swaps
597		for (i = 0; i < swaps.length; i++) {
598			if (swaps[i].length > 0) {
599				var moduleIdx = $.fn.liveTile.contentModules.hasContentModule(swaps[i]);
600				if (moduleIdx > -1) {
601					tdata.contentModules.push($.fn.liveTile.contentModules.modules[moduleIdx]);
602				}
603			}
604		}
605		// set the initDelay value to the delay if it's not set
606		tdata.initDelay = useData ? this.getDataOrDefault($tile, "initdelay", stgs.initDelay) : stgs.initDelay;
607		// if the delay is -1 call the triggerDelay function to get a value
608		if (tdata.delay < -1)
609			tdata.delay = stgs.triggerDelay(1);
610		else if (tdata.delay < 0)
611			tdata.delay = 3500 + (Math.random() * 4501);
612		// match the delay value if less than 0
613		if (tdata.initDelay < 0)
614			tdata.initDelay = tdata.delay;
615		// merge the objects
616		var mergedData = {};
617		for (i = 0; i < tdata.contentModules.length; i++)
618			$.extend(mergedData, tdata.contentModules[i].data);
619		$.extend(mergedData, stgs, tdata);
620		// add flip-list / carousel data
621		var $tiles;
622		if (mergedData.mode === "flip-list") {
623			$tiles = $tile.find(mergedData.tileSelector).not(".tile-title");
624			$tiles.each(function (idx, ele) {
625				var $li = $(ele);
626				var ldata = {
627					direction: useData ? privMethods.getDataOrDefault($li, "direction", mergedData.direction) : mergedData.direction,
628					newWindow: useData ? privMethods.getDataOrDefault($li, "new-window", false) : false,
629					link: useData ? privMethods.getDataOrDefault($li, "link", "") : "",
630					faces: { $front: null, $back: null },
631					height: $li.height(),
632					width: $li.width(),
633					isReversed: false
634				};
635				ldata.margin = ldata.direction === "vertical" ? ldata.height / 2 : ldata.width / 2;
636				mergedData.listData.push(ldata);
637			});
638		} else if (mergedData.mode === "carousel") {
639			mergedData.stack = true;
640			$tiles = $tile.find(mergedData.tileSelector).not(".tile-title");
641			$tiles.each(function (idx, ele) {
642				var $slide = $(ele);
643				var sdata = {
644					bounce: useData ? privMethods.getDataOrDefault($slide, "bounce", false) : false,
645					bounceDirections: useData ? privMethods.getDataOrDefault($slide, "bounce-dir", "all") : "all",
646					link: useData ? privMethods.getDataOrDefault($slide, "link", "") : "",
647					newWindow: useData ? privMethods.getDataOrDefault($slide, "new-window", false) : false,
648					animationDirection: useData ? privMethods.getDataOrDefault($slide, "ani-direction", "") : "",
649					direction: useData ? privMethods.getDataOrDefault($slide, "direction", "") : ""
650				};
651				mergedData.listData.push(sdata);
652			});
653		}
654		// get any additional options from the modules
655		for (i = 0; i < tdata.contentModules.length; i++) {
656			if (typeof (mergedData.contentModules[i].initData) === "function")
657				mergedData.contentModules[i].initData(mergedData, $tile);
658		}
659		tdata = null;
660		return mergedData;
661	},
662	prepTile: function ($tile, tdata) {
663		//add the mode to the tile if it's not already there.
664		$tile.addClass(tdata.mode);
665		var ret = {
666			$tileFaces: null,     // all possible tile faces in a liveTile in a non list mode
667			$listTiles: null,     // all possible tiles in a liveTile in a list mode
668			$front: null,         // the front face of a tile in a non list mode
669			$back: null          // the back face of a tile in a non list mode
670		};
671		var rotateDir, frontCss, backCss, tileCss;
672		// prepare the tile based on the current mode
673		switch (tdata.mode) {
674			case "fade":
675				// front and back tile faces
676				ret.$tileFaces = $tile.find(tdata.tileFaceSelector).not(".tile-title");
677				ret.$front = (tdata.faces.$front != null && tdata.faces.$front.length > 0) ?
678                               tdata.faces.$front.addClass('fade-front') :
679                               ret.$tileFaces.filter(":first").addClass('fade-front');
680				// get back face from settings, via selector, or append it if necessary
681				if (tdata.faces.$back != null && tdata.faces.$back.length > 0)    // use $back option
682					ret.$back = tdata.faces.$back.addClass('fade-back');
683				else if (ret.$tileFaces.length > 1)                             // get the last tile face
684					ret.$back = ret.$tileFaces.filter(":last").addClass('fade-back');
685				else if (tdata.appendBack)                                       // append the back t
685ile
686					ret.$back = $('<div class="fade-back"></div>').appendTo($tile);
687				else                                                            // just keep an empty placeholder
688					ret.$back = $('<div></div>');
689				break;
690			case "slide":
691				// front and back tile faces
692				ret.$tileFaces = $tile.find(tdata.tileFaceSelector).not(".tile-title");
693				// get front face from settings or via selector
694				ret.$front = (tdata.faces.$front != null && tdata.faces.$front.length > 0) ?
695                                tdata.faces.$front.addClass('slide-front') :
696                                ret.$tileFaces.filter(":first").addClass('slide-front'); // using :first for pre jQuery 1.4
697				// get back face from settings, via selector, or append it if necessary
698				if (tdata.faces.$back != null && tdata.faces.$back.length > 0)    // use $back option
699					ret.$back = tdata.faces.$back.addClass('slide-back');
700				else if (ret.$tileFaces.length > 1)                             // get the last tile face
701					ret.$back = ret.$tileFaces.filter(":last").addClass('slide-back');
702				else if (tdata.appendBack)                                       // append the back tile
703					ret.$back = $('<div class="slide-back"></div>').appendTo($tile);
704				else                                                            // just keep an empty placeholder
705					ret.$back = $('<div></div>');
706				// stack mode
707				if (tdata.stack == true) {
708					var prop,
709                        translate;
710					if (tdata.direction === "vertical") {
711						prop = "top",
712                        translate = 'translate(0%, -100%) translateZ(0)';
713					} else {
714						prop = "left",
715                        translate = 'translate(-100%, 0%) translateZ(0)';
716					}
717					backCss = {};
718					if (tdata.useTranslate)
719						helperMethods.appendStyleProperties(backCss, ['transform'], [translate]);
720					else
721						backCss[prop] = "-100%";
722					ret.$back.css(backCss);
723				}
724				$tile.data("metrojs.tile", { animating: false });
725				if (metrojs.capabilities.canTransition && tdata.useHardwareAccel) {   // hardware accelerated :)                        
726					$tile.addClass("ha");
727					ret.$front.addClass("ha");
728					ret.$back.addClass("ha");
729				}
730				break;
731			case "carousel":
732				ret.$listTiles = $tile.find(tdata.tileSelector).not(".tile-title");
733				var numberOfSlides = ret.$listTiles.length;
734				$tile.data("metrojs.tile", { animating: false });
735				tdata.currentIndex = Math.min(tdata.currentIndex, numberOfSlides - 1);
736				ret.$listTiles.each(function (idx, ele) {
737					var $slide = $(ele).addClass("slide");
738					var sdata = tdata.listData[idx],
739                        aniDir = typeof (sdata.animationDirection) === "string" && sdata.animationDirection.length > 0 ? sdata.animationDirection : tdata.animationDirection,
740                        dir = typeof (sdata.direction) === "string" && sdata.direction.length > 0 ? sdata.direction : tdata.direction;
741					if (idx == tdata.currentIndex) {
742						$slide.addClass("active");
743					} else if (aniDir === "forward") {
744						if (dir === "vertical") {
745							tileCss = tdata.useTranslate ? helperMethods.appendStyleProperties({}, ['transform'], ['translate(0%, 100%) translateZ(0)']) :
746                                                   { left: '0%', top: '100%' };
747							$slide.css(tileCss);
748						} else {
749							tileCss = tdata.useTranslate ? helperMethods.appendStyleProperties({}, ['transform'], ['translate(100%, 0%) translateZ(0)']) :
750                                                   { left: '100%', top: '0%' };
751							$slide.css(tileCss);
752						}
753					} else if (aniDir === "backward") {
754						if (dir === "vertical") {
755							tileCss = tdata.useTranslate ? helperMethods.appendStyleProperties({}, ['transform'], ['translate(0%, -100%) translateZ(0)']) :
756                                                   { left: '0%', top: '-100%' };
757							$slide.css(tileCss);
758						} else {
759							tileCss = tdata.useTranslate ? helperMethods.appendStyleProperties({}, ['transform'], ['translate(-100%, 0%) translateZ(0)']) :
760                                                   { left: '-100%', top: '0%' };
761							$slide.css(tileCss);
762						}
763					}
764					// link and bounce can be bound per slide
765					// add the click handler and link property
766					privMethods.bindLink($slide, sdata);
767					// add the bounce effect
768					if (tdata.useHardwareAccel && metrojs.capabilities.canTransition)
769						privMethods.bindBounce($slide, sdata);
770					$slide = null;
771					sdata = null;
772				});
773				// hardware accelerated :)
774				if (metrojs.capabilities.canFlip3d && tdata.useHardwareAccel) {
775					$tile.addClass("ha");
776					ret.$listTiles.addClass("ha");
777				}
778				break;
779			case "flip-list":
780				// the tile containers inside the list
781				ret.$listTiles = $tile.find(tdata.tileSelector).not(".tile-title");
782				ret.$listTiles.each(function (idx, ele) {
783					var $li = $(ele).addClass("tile-" + (idx + 1));
784					// add the flip class to the front face
785					var $lFront = $li.find(tdata.tileFaceSelector).filter(":first").addClass("flip-front").css({ margin: "0px" });
786					// append a back tile face if one isnt present
787					if ($li.find(tdata.tileFaceSelector).length === 1 && tdata.appendBack == true)
788						$li.append("<div></div>");
789					// add the flip class to the back face
790					var $lBack = $li.find(tdata.tileFaceSelector).filter(":last").addClass("flip-back").css({ margin: "0px" });
791					// update the tdata object with the faces
792					tdata.listData[idx].faces.$front = $lFront;
793					tdata.listData[idx].faces.$back = $lBack;
794					// set data for overrides and easy access
795					$li.data("metrojs.tile", {
796						animating: false,
797						count: 1,
798						completeTimeout: null,
799						flCompleteTimeout: null,
800						index: idx
801					});
802					var ldata = $li.data("metrojs.tile");
803					// add the hardware accelerated classes
804					if (metrojs.capabilities.canFlip3d && tdata.useHardwareAccel) {   // hardware accelerated :)
805						$li.addClass("ha");
806						$lFront.addClass("ha");
807						$lBack.addClass("ha");
808						rotateDir = tdata.listData[idx].direction === "vertical" ? "rotateX(180deg)" : "rotateY(180deg)";
809						backCss = helperMethods.appendStyleProperties({}, ["transform"], [rotateDir]);
810						$lBack.css(backCss);
811					} else { // not hardware accelerated :(
812						// the front tile face will take up the entire tile
813						frontCss = (tdata.listData[idx].direction === "vertical") ?
814				{ height: '100%', width: '100%', marginTop: '0px', opacity: '1' } :
815				{ height: '100%', width: '100%', marginLeft: '0px', opacity: '1' };
816						// the back tile face is hidden by default and expanded halfway through a flip
817						backCss = (tdata.listData[idx].direction === "vertical") ?
818				{ height: '0px', width: '100%', marginTop: tdata.listData[idx].margin + 'px', opacity: tdata.noHAflipOpacity } :
819				{ height: '100%', width: '0px', marginLeft: tdata.listData[idx].margin + 'px', opacity: tdata.noHAflipOpacity };
820						$lFront.css(frontCss);
821						$lBack.css(backCss);
822					}
823					var flipEnded = function () {
824						ldata.count++;
825						if (ldata.count >= MAX_LOOP_COUNT)
826							ldata.count = 1;
827					};
828					if (tdata.flipListOnHover) {
829						var event = tdata.flipListOnHoverEvent + ".liveTile";
830						$lFront.bind(event, function () {
831							privMethods.flip($li, ldata.count, tdata, flipEnded);
832						});
833						$lBack.bind(event, function () {
834							privMethods.flip($li, ldata.count, tdata, flipEnded);
835						});
836					}
837					if (tdata.listData[idx].link.length > 0) {
838						$li.css({ cursor: 'pointer' }).bind("click.liveTile", function () {
839							if (tdata.listData[idx].newWindow)
840								window.open(tdata.listData[idx].link);
841							else
842								window.location = tdata.listData[idx].link;
843						});
844					}
845				});
846				break;
847			case "flip":
848				// front and back tile faces
849				ret.$tileFaces = $tile.find(tdata.tileFaceSelector).not(".tile-title");
850				// get front face from settings or via selector
851				ret.$front = (tdata.faces.$front != null && tdata.faces.$front.length > 0) ?
852                                tdata.faces.$front.addClass('flip-front') :
853                                ret.$tileFaces.filter(":first").addClass('flip-front');
854				// get back face from settings, via selector, or append it if necessary
855				if (tdata.faces.$back != null && tdata.faces.$back.length > 0) {
856					// use $back option
857					ret.$back = tdata.faces.$back.addClass('flip-back');
858				} else if (ret.$tileFaces.length > 1) {
859					// get the last tile face
860					ret.$back = ret.$tileFaces.filter(":last").addClass('flip-back');
861				} else if (tdata.appendBack) {
862					// append the back tile
863					ret.$back = $('<div class="flip-back"></div>').appendTo($tile);
864				} else {
865					// just keep an empty placeholder
866					ret.$back = $('<div></div>');
867				}
868				$tile.data("metrojs.tile", { animating: false });
869				if (metrojs.capabilities.canFlip3d && tdata.useHardwareAccel) {
870					// hardware accelerated :)
871					$tile.addClass("ha");
872					ret.$front.addClass("ha");
873					ret.$back.addClass("ha");
874					rotateDir = tdata.direction === "vertical" ? "rotateX(180deg)" : "rotateY(180deg)";
875					backCss = helperMethods.appendStyleProperties({}, ["transform"], [rotateDir]);
876					ret.$back.css(backCss);
877
878				} else {
879					// not hardware accelerated :(
880					// the front tile face will take up the entire tile
881					frontCss = (tdata.direction === "vertical") ?
882			{ height: '100%', width: '100%', marginTop: '0px', opacity: '1' } :
883			{ height: '100%', width: '100%', marginLeft: '0px', opacity: '1' };
884					// the back tile face is hidden by default and expanded halfway through a flip
885					backCss = (tdata.direction === "vertical") ?
886			{ height: '0%', width: '100%', marginTop: tdata.margin + 'px', opacity: '0' } :
887			{ height: '100%', width: '0%', marginLeft: tdata.margin + 'px', opacity: '0' };
888					ret.$front.css(frontCss);
889					ret.$back.css(backCss);
890				}
891				break;
892		}
893		return ret;
894	},
895	bindPauseOnHover: function ($tile) {
896		// stop the tile when hovered and resume after a delay
897		(function () {
898			var data = $tile.data("LiveTile"),
899                isOver = false,
900                isPending = false,
901                pauseIn = (data.pauseOnHoverEvent == "both" || data.pauseOnHoverEvent == "mouseover" || data.pauseOnHoverEvent == "mouseenter"),
902                pauseOut = (data.pauseOnHoverEvent == "both" || data.pauseOnHoverEvent == "mouseout" || data.pauseOnHoverEvent == "mouseleave");
903			data.pOnHoverMethods = {
904				pause: function () {
905					data.timer.pause();
906					if (data.mode === "flip-list") {
907						data.faces.$listTiles.each(function (idx, li) {
908							window.clearTimeout($(li).data("metrojs.tile").completeTimeout);
909						});
910					}
911				},
912				over: function (e) {
913					if (isOver || isPending)
914						return;
915					if (data.runEvents) {
916						isPending = true;
917						data.eventTimeout = window.setTimeout(function () {
918							isPending = false;
919							if (pauseOut)
920								isOver = true;
921							data.pOnHoverMethods.pause();
922						}, data.onHoverDelay);
923					}
924				},
925				out: function (e) {
926					if (isPending) {
927						window.clearTimeout(data.eventTimeout);
928						isPending = false;
929						return;
930					}
931					if (pauseIn) {
932						if (!isOver && !isPending)
933							return;
934						if (data.runEvents) {
935							// todo: use a custom value if provided
936							data.timer.start(data.hasRun ? data.delay : data.initDelay);
937						}
938					} else {
939						data.pOnHoverMethods.pause();
940					}
941					isOver = false;
942				}
943			};
944			if (!metrojs.capabilities.canTouch) {
945				if (pauseIn)
946					$tile.bind("mouseover.liveTile", data.pOnHoverMethods.over);
947				if (pauseOut)
948					$tile.bind("mouseout.liveTile", data.pOnHoverMethods.out);
949			} else {
950				if (window.navigator.msPointerEnabled) { // pointer
951					if (pauseIn)
952						$tile[0].addEventListener('MSPointerOver', data.pOnHoverMethods.over, false);
953					if (pauseOut)
954						$tile[0].addEventListener('MSPointerOut', data.pOnHoverMethods.out, false);
955				} else { // touch events
956					if (pauseIn)
957						$tile.bind("touchstart.liveTile", data.pOnHoverMethods.over);
958					if (pauseOut)
959						$tile.bind("touchend.liveTile", data.pOnHoverMethods.out);
960				}
961			}
962		})();
963	},
964	unbindMsPauseOnHover: function ($tile, data) {
965		if (typeof (data.pOnHoverMethods) !== "undefined" && window.navigator.msPointerEnabled) {
966			$tile[0].removeEventListener('MSPointerOver', data.pOnHoverMethods.over, false);
967			$tile[0].removeEventListener('MSPointerOut', data.pOnHoverMethods.out, false);
968		}
969	},
970	bindPlayOnHover: function ($tile, data) {
971		// play the tile immediately when hovered
972		(function () {
973			var isOver = false,
974                isPending = false,
975                playIn = (data.playOnHoverEvent == "both" || data.playOnHoverEvent == "mouseover" || data.playOnHoverEvent == "mouseenter"),
976                playOut = (data.playOnHoverEvent == "both" || data.playOnHoverEvent == "mouseout" || data.playOnHoverEvent == "mouseleave");
977			data.onHoverMethods = {
978				over: function (event) {
979					if (isOver || isPending || (data.bounce && data.bounceMethods.down != "no"))
980						return;
981					// if startNow is set use the opposite of isReversed so we're in sync            
982					var rev = (data.mode == "flip") || (data.startNow ? !data.isReversed : data.isReversed);
983					window.clearTimeout(data.eventTimeout);
984					if ((data.runEvents && rev) || !data.hasRun) {
985						isPending = true;
986						data.eventTimeout = window.setTimeout(function () {
987							isPending = false;
988							if (playOut)
989								isOver = true;
990							pubMethods["play"].apply($tile[0], [0]);
991						}, data.onHoverDelay);
992					}
993				},
994				out: function (event) {
995					if (isPending) {
996						window.clearTimeout(data.eventTimeout);
997						isPending = false;
998						return;
999					}
1000					if (playIn) {
1001						if (!isOver && !isPending) {
1002							return;
1003						}
1004					}
1005					window.clearTimeout(data.eventTimeout);
1006					data.eventTimeout = window.setTimeout(function () {
1007						var rev = (data.mode == "flip") || (data.startNow ? data.isReversed : !data.isReversed);
1008						if (data.runEvents && rev) {
1009							pubMethods["play"].apply($tile[0], [0]);
1010						}
1011						isOver = false;
1012					}, data.speed + data.onHoverOutDelay);
1013				}
1014			};
1015			if (!metrojs.capabilities.canTouch) {
1016				if (playIn)
1017					$tile.bind('mouseenter.liveTile', data.onHoverMethods.over);
1018				if (playOut)
1019					$tile.bind('mouseleave.liveTile', data.onHoverMethods.out);
1020			} else {
1021				if (window.navigator.msPointerEnabled) { // pointer
1022					if (playIn)
1023						$tile[0].addEventListener('MSPointerDown', data.onHoverMethods.over, false);
1024					// mouseleave gives a more consistent effect than out when the children are transformed
1025					if (playOut)
1026						$tile.bind("mouseleave.liveTile", data.onHoverMethods.out);
1027				} else { // touch events
1028					if (playIn)
1029						$tile.bind("touchstart.liveTile", data.onHoverMethods.over);
1030					if (playOut)
1031						$tile.bind("touchend.liveTile", data.onHoverMethods.out);
1032				}
1033
1034			}
1035		})();
1036	},
1037	unbindMsPlayOnHover: function ($tile, data) {
1038		if (typeof (data.onHoverMethods) !== "undefined" && window.navigator.msPointerEnabled) {
1039			$tile[0].removeEventListener('MSPointerDown', data.onHoverMethods.over, false);
1040		}
1041	},
1042	bindBounce: function ($tile, data) {
1043		// add bounce
1044		if (data.bounce) {
1045			$tile.addClass("bounce");
1046			$tile.addClass("noselect");
1047			(function () {
1048				data.bounceMethods = {
1049					down: "no",
1050					threshold: 30,
1051					zeroPos: { x: 0, y: 0 },
1052					eventPos: { x: 0, y: 0 },
1053					inTilePos: { x: 0, y: 0 },
1054					pointPos: { x: 0, y: 0 },
1055					regions: {
1056						c: [0, 0],      // center
1057						tl: [-1, -1],   // top left
1058						tr: [1, -1],    // top right
1059						bl: [-1, 1],    // bottom left
1060						br: [1, 1],     // bottom right
1061						t: [null, -1],  // top
1062						r: [1, null],   // right
1063						b: [null, 1],   // bottom
1064						l: [-1, null]   // left
1065					},
1066					targets: {
1067						all: ['c', 't', 'r', 'b', 'l', 'tl', 'tr', 'bl', 'br'],
1068						edges: ['c', 't', 'r', 'b', 'l'],
1069						corners: ['c', 'tl', 'tr', 'bl', 'br']
1070					},
1071					hitTest: function ($el, pos, targetRegions, omegaC) {
1072						var regions = data.bounceMethods.regions,
1073                            checkFor = data.bounceMethods.targets[targetRegions],
1074                            i = 0,
1075                            strictMatch = null,
1076                            looseMatch = null,
1077                            defResult = { hit: [0, 0], name: 'c' };
1078						// scale only for android 2.x and old ie
1079						if (metrojs.capabilities.isOldAndroid || !metrojs.capabilities.canTransition)
1080							return defResult;
1081						if (typeof (checkFor) == "undefined") {
1082							if (typeof (targetRegions) === "string")
1083								checkFor = targetRegions.split(',');
1084							// only default to center if explicitly requested
1085							if ($.isArray(checkFor) && $.inArray('c') == -1) {
1086								omegaC = 0;
1087								defResult = null;
1088							}
1089						}
1090						// check for a matching region
1091						var w = $el.width(),
1092                           h = $el.height(),
1093                           // center threshold -  maximum amount from center
1094                           ct = [w * omegaC, h * omegaC],
1095                           // how far from the center is the point
1096                           diffX = pos.x - (w * 0.5),
1097                           diffY = pos.y - (h * 0.5),
1098                            // if we're beyond the center threshold, set -1 or 1 else 0
1099                           hit = [
1100                               diffX > 0 ? (Math.abs(diffX) <= ct[0] ? 0 : 1) : (Math.abs(diffX) <= ct[0] ? 0 : -1),
1101                               diffY > 0 ? (Math.abs(diffY) <= ct[1] ? 0 : 1) : (Math.abs(diffY) <= ct[1] ? 0 : -1)
1102                           ];
1103						for (; i < checkFor.length; i++) {
1104							if (strictMatch != null)
1105								return strictMatch;
1106							var r = checkFor[i],
1107                                region = regions[r];
1108							if (r == "*") {
1109								r = checkFor[i + 1];
1110								return { region: regions[r], name: r };
1111							}
1112							if (hit[0] == region[0] && hit[1] == region[1]) {
1113								// found the region with a strict lookup
1114								strictMatch = { hit: region, name: r };
1115							} else if ((hit[0] == region[0] || region[0] == null) && (hit[1] == region[1] || region[1] == null)) {
1116								// found the region with a loose lookup
1117								looseMatch = { hit: region, name: r };
1118							}
1119						}
1120						// prefer a strict match
1121						if (strictMatch != null)
1122							return strictMatch;
1123						else if (looseMatch != null)
1124							return looseMatch;
1125						else // no matches were found, return center
1126							return defResult;
1127					},
1128					bounceDown: function (e) {
1129						if (e.target.tagName == "A" && !$(e).is(".bounce"))
1130							return;
1131						var point = e.originalEvent && e.originalEvent.touches ? e.originalEvent.touches[0] : e,
1132                            offsetOfTile = $tile.offset(),
1133                            scrollX = window.pageXOffset,
1134                            scrollY = window.pageYOffset;
1135						data.bounceMethods.pointPos = {
1136							x: point.pageX,
1137							y: point.pageY
1138						};
1139						data.bounceMethods.inTilePos = {
1140							x: point.pageX - offsetOfTile.left,
1141							y: point.pageY - offsetOfTile.top
1142						};
1143
1144						if (!data.$tileParent) {
1145							data.$tileParent = $tile.parent();
1146						}
1147						var offsetOfParent = data.$tileParent.offset();
1148						data.bounceMethods.eventPos = {
1149							x: (offsetOfTile.left - offsetOfParent.left) + ($tile.width() / 2),
1150							y: (offsetOfTile.top - offsetOfParent.top) + ($tile.height() / 2)
1151						};
1152						var hit = data.bounceMethods.hitTest($tile, data.bounceMethods.inTilePos, data.bounceDirections, 0.25);
1153						if (hit == null)
1154							data.bounceMethods.down = "no";
1155						else {
1156							if (window.navigator.msPointerEnabled) {
1157								document.addEventListener('MSPointerUp', data.bounceMethods.bounceUp, false);
1158								$tile[0].addEventListener('MSPointerUp', data.bounceMethods.bounceUp, false);
1159								document.addEventListener('MSPointerCancel', data.bounceMethods.bounceUp, false);
1160								if (data.bounceFollowsMove)
1161									$tile[0].addEventListener('MSPointerMove', data.bounceMethods.bounceMove, false);
1162							} else {
1163								$(document).bind("mouseup.liveTile, touchend.liveTile, touchcancel.liveTile, dragstart.liveTile", data.bounceMethods.bounceUp);
1164								if (data.bounceFollowsMove) {
1165									$tile.bind("touchmove.liveTile", data.bounceMethods.bounceMove);
1166									$tile.bind("mousemove.liveTile", data.bounceMethods.bounceMove);
1167								}
1168							}
1169							var bClass = "bounce-" + hit.name;
1170							$tile.addClass(bClass);
1171							data.bounceMethods.down = bClass;
1172							data.bounceMethods.downPcss = helperMethods.appendStyleProperties({}, ['perspective-origin'], [data.bounceMethods.eventPos.x + "px " + data.bounceMethods.eventPos.y + "px"]);
1173							data.$tileParent.css(data.bounceMethods.downPcss);
1174						}
1175					},
1176					bounceUp: function () {
1177						if (data.bounceMethods.down != "no") {
1178							data.bounceMethods.unBounce();
1179							if (window.navigator.msPointerEnabled) {
1180								document.removeEventListener('MSPointerUp', data.bounceMethods.bounceUp, false);
1181								$tile[0].removeEventListener('MSPointerUp', data.bounceMethods.bounceUp, false);
1182								document.removeEventListener('MSPointerCancel', data.bounceMethods.bounceUp, false);
1183								if (data.bounceFollowsMove)
1184									$tile[0].removeEventListener('MSPointerMove', data.bounceMethods.bounceMove, false);
1185
1186							} else
1187								$(document).unbind("mouseup.liveTile, touchend.liveTile, touchcancel.liveTile, dragstart.liveTile", data.bounceMethods.bounceUp);
1188							if (data.bounceFollowsMove) {
1189								$tile.unbind("touchmove.liveTile", data.bounceMethods.bounceMove);
1190								$tile.unbind("mousemove.liveTile", data.bounceMethods.bounceMove);
1191							}
1192						}
1193					},
1194					bounceMove: function (e) {
1195						if (data.bounceMethods.down != "no") {
1196							var point = e.originalEvent && e.originalEvent.touches ? e.originalEvent.touches[0] : e,
1197                                x = Math.abs(point.pageX - data.bounceMethods.pointPos.x),
1198                                y = Math.abs(point.pageY - data.bounceMethods.pointPos.y);
1199							if (x > data.bounceMethods.threshold || y > data.bounceMethods.threshold) {
1200								var bounceClass = data.bounceMethods.down;
1201								data.bounceMethods.bounceDown(e);
1202								if (bounceClass != data.bounceMethods.down)
1203									$tile.removeClass(bounceClass);
1204							}
1205						}
1206					},
1207					unBounce: function () {
1208						$tile.removeClass(data.bounceMethods.down);
1209						if (typeof (data.bounceMethods.downPcss) == "object") {
1210							var names = ['perspective-origin', 'perspective-origin-x', 'perspective-origin-y'],
1211                                vals = ['', '', ''];
1212							data.bounceMethods.downPcss = helperMethods.appendStyleProperties({}, names, vals);
1213							// let the bounce finish and then strip out the perspective
1214							window.setTimeout(function () {
1215								data.$tileParent.css(data.bounceMethods.downPcss);
1216							}, 200);
1217						}
1218						data.bounceMethods.down = "no";
1219						data.bounceMethods.inTilePos = data.bounceMethods.zeroPos;
1220						data.bounceMethods.eventPos = data.bounceMethods.zeroPos;
1221					}
1222				};
1223				// IE 10+
1224				if (window.navigator.msPointerEnabled) {// touch only -> // && window.navigator.msMaxTouchPoints) {
1225					$tile[0].addEventListener('MSPointerDown', data.bounceMethods.bounceDown, false);
1226				} else if (metrojs.capabilities.canTouch) {
1227					// everybody else                    
1228					$tile.bind("touchstart.liveTile", data.bounceMethods.bounceDown);
1229
1230				} else {
1231					$tile.bind("mousedown.liveTile", data.bounceMethods.bounceDown);
1232				}
1233			})();
1234		}
1235	},
1236	unbindMsBounce: function ($tile, data) {
1237		if (data.bounce && window.navigator.msPointerEnabled) {// touch only -> // && window.navigator.msMaxTouchPoints) {
1238			$tile[0].removeEventListener('MSPointerDown', data.bounceMethods.bounceDown, false);
1239			$tile[0].removeEventListener('MSPointerCancel', data.bounceMethods.bounceUp, false);
1240			$tile[0].removeEventListener('MSPointerOut', data.bounceMethods.bounceUp, false);
1241			//$tile[0].removeEventListener('MSPointerMove', data.bounceMethods.bounceMove, false);
1242		}
1243	},
1244	bindLink: function ($tile, data) {
1245		if (data.link.length > 0) {
1246			$tile.css({ cursor: 'pointer' }).bind("click.liveTile", function (e) {
1247				if (e.target.tagName == "A" && !$(e).is(".live-tile,.slide,.flip"))
1248					return;
1249				if (data.newWindow)
1250					window.open(data.link);
1251				else
1252					window.location = data.link;
1253			});
1254		}
1255	},
1256	runContenModules: function (data, $front, $back, index) {
1257		for (var i = 0; i < data.contentModules.length; i++) {
1258			var currentModule = data.contentModules[i];
1259			if (typeof (currentModule.action) == "function")
1260				currentModule.action(data, $front, $back, index);
1261		}
1262	},
1263	fade: function ($tile, count, data) {
1264		var tdata = typeof (data) === "object" ? data : $tile.data("LiveTile"),
1265            resumeTimer = function () {
1266            	// if the tile should run again start the timer back with the current delay
1267            	if (tdata.timer.repeatCount > 0 || tdata.timer.repeatCount == -1) {
1268            		if (tdata.timer.count != tdata.timer.repeatCount) {
1269            			tdata.timer.start(tdata.delay);
1270            		}
1271            	}
1272            };
1273		if (typeof (tdata) === "undefined") {
1274			throwError($.fn.liveTile.defaults.rebindMessage);
1275			return;
1276		}
1277		if (tdata.faces.$front.is(":animated"))
1278			return;
1279		tdata.timer.pause();
1280		var loopCount = tdata.loopCount + 1;
1281		tdata.isReversed = loopCount % 2 === 0; // the count starts at 1
1282		var start = tdata.animationStarting.call($tile[0], tdata, tdata.faces.$front, tdata.faces.$back);
1283		if (typeof (start) != "undefined" && start == false) {
1284			resumeTimer();
1285			return;
1286		}
1287		tdata.loopCount = loopCount;
1288		var faded = function () {
1289			resumeTimer();
1290			// run content modules and animationComplete callback
1291			privMethods.runContenModules(tdata, tdata.faces.$front, tdata.faces.$back);
1292			tdata.animationComplete.call($tile[0], tdata, tdata.faces.$front, tdata.faces.$back);
1293		};
1294		if (tdata.isReversed)
1295			tdata.faces.$front.fadeIn(tdata.speed, tdata.noHaTransFunc, faded);
1296		else
1297			tdata.faces.$front.fadeOut(tdata.speed, tdata.noHaTransFunc, faded);
1298	},
1299	slide: function ($tile, count, data, stopIndex, callback) {
1300		var tdata = typeof (data) === "object" ? data : $tile.data("LiveTile"),
1301            aniData = $tile.data("metrojs.tile");
1302		if (typeof (tdata) === "undefined") {
1303			throwError($.fn.liveTile.defaults.rebindMessage);
1304			return;
1305		}
1306		if (aniData.animating == true || $tile.is(":animated")) {
1307			tdata = null;
1308			aniData = null;
1309			return;
1310		}
1311		var resumeTimer = function () {
1312			// if the tile should run again start the timer back with the current delay
1313			if (tdata.timer.repeatCount > 0 || tdata.timer.repeatCount == -1) {
1314				if (tdata.timer.count != tdata.timer.repeatCount) {
1315					tdata.timer.start(tdata.delay);
1316				}
1317			}
1318		};
1319		if (tdata.mode !== "carousel") {
1320			tdata.isReversed = tdata.currentIndex % 2 !== 0;  // the count starts at 1
1321			// carousel mode maintains its own timer
1322			tdata.timer.pause();
1323			var start = tdata.animationStarting.call($tile[0], tdata, tdata.faces.$front, tdata.faces.$back);
1324			if (typeof (start) != "undefined" && start == false) {
1325				resumeTimer();
1326				return;
1327			}
1328			tdata.loopCount = tdata.loopCount + 1;
1329		} else {
1330			// in carousel mode the face that just left the stage is always the $back
1331			tdata.isReversed = true;
1332		}
1333		// get temp values passed in from data methods
1334		var direction;
1335		if (typeof (tdata.tempValues.direction) === "string" && tdata.tempValues.direction.length > 0)
1336			direction = tdata.tempValues.direction;
1337		else
1338			direction = tdata.direction;
1339		tdata.tempValues.direction = null;
1340		var css = {},
1341            cssback = {},
1342            // the stop index is overridden in carousel mode
1343            stopIdx = typeof (stopIndex) === "undefined" ? tdata.currentIndex : stopIndex,
1344            stop = $.trim(tdata.stops[Math.min(stopIdx, tdata.stops.length - 1)]),
1345            pxIdx = stop.indexOf('px'),
1346            offset = 0,
1347            amount = 0,
1348            metric = (direction === "vertical") ? tdata.height : tdata.width,
1349            tProp = (direction === "vertical") ? "top" : "left",
1350            stack = tdata.stack == true;
1351		// when the slide is complete increment the index or call the callback
1352		var slideFinished = function () {
1353			if (typeof (callback) === "undefined") {
1354				tdata.currentIndex = tdata.currentIndex + 1;
1355				if (tdata.currentIndex > tdata.stops.length - 1) {
1356					tdata.currentIndex = 0;
1357				}
1358			} else {
1359				callback();
1360			}
1361			if (tdata.mode != "carousel") {
1362				resumeTimer();
1363			}
1364			// run content modules and animationComplete callback            
1365			privMethods.runContenModules(tdata, tdata.faces.$front, tdata.faces.$back, tdata.currentIndex);
1366			tdata.animationComplete.call($tile[0], tdata, tdata.faces.$front, tdata.faces.$back);
1367			tdata = null;
1368			aniData = null;
1369		};
1370		if (pxIdx > 0) {
1371			amount = parseInt(stop.substring(0, pxIdx), 10);
1372			offset = (amount - metric) + 'px';
1373		} else {
1374			//is a percentage
1375			amount = parseInt(stop.replace('%', ''), 10);
1376			offset = (amount - 100) + '%';
1377		}
1378		// hardware accelerated :)
1379		if (metrojs.capabilities.canTransition && tdata.useHardwareAccel) {
1380			if (typeof (aniData.animating) !== "undefined" && aniData.animating == true)
1381				return;
1382			aniData.animating = true;
1383			var props = ['transition-property', 'transition-duration', 'transition-timing-function'],
1384                vals = [tdata.useTranslate ? "transform" : tProp, tdata.speed + 'ms', tdata.haTransFunc];
1385			vals[helperMethods.browserPrefix + 'transition-property'] = helperMethods.browserPrefix + "transform";
1386			css = helperMethods.appendStyleProperties(css, props, vals);
1387			cssback = helperMethods.appendStyleProperties(cssback, props, vals);
1388			var vertical = direction === "vertical",
1389                prop = vertical ? "top" : "left",
1390                translateTo;
1391			if (!tdata.useTranslate) {
1392				css[prop] = stop;
1393				if (stack)
1394					cssback[prop] = offset;
1395			} else {
1396				translateTo = vertical ? "translate(0%, " + stop + ")" : "translate(" + stop + ", 0%)";
1397				css = helperMethods.appendStyleProperties(css, ['transform'], [translateTo + "translateZ(0)"]);
1398				if (stack) {
1399					translateTo = vertical ? "translate(0%, " + offset + ")" : "translate(" + offset + ", 0%)";
1400					cssback = helperMethods.appendStyleProperties(cssback, ['transform'], [translateTo + "translateZ(0)"]);
1401				}
1402			}
1403			tdata.faces.$front.css(css);
1404			if (stack)
1405				tdata.faces.$back.css(cssback);
1406			window.clearTimeout(tdata.completeTimeout);
1407			tdata.completeTimeout = window.setTimeout(function () {
1408				aniData.animating = false;
1409				slideFinished();
1410			}, tdata.speed);
1411		} else {
1412			// not hardware accelerated :(
1413			css[tProp] = stop;
1414			cssback[tProp] = offset;
1415			aniData.animating = true;
1416			var $front = tdata.faces.$front.stop(),
1417                $back = tdata.faces.$back.stop();
1418			$front.animate(css, tdata.speed, tdata.noHaTransFunc, function () {
1419				aniData.animating = false;
1420				slideFinished();
1421			});
1422			// change the css value to the offset
1423			if (stack)
1424				$back.animate(cssback, tdata.speed, tdata.noHaTransFunc, function () { });
1425		}
1426	},
1427	carousel: function ($tile, count) {
1428		var tdata = $tile.data("LiveTile");
1429		if (typeof (tdata) === "undefined") {
1430			throwError($.fn.liveTile.defaults.rebindMessage);
1431			return;
1432		}
1433		// dont update css or call slide if animated or if there's only one face
1434		var aniData = $tile.data("metrojs.tile");
1435		if (aniData.animating == true || tdata.faces.$listTiles.length <= 1) {
1436			aniData = null;
1437			return;
1438		}
1439		var resumeTimer = function () {
1440			if (tdata.timer.repeatCount > 0 || tdata.timer.repeatCount == -1) {
1441				if (tdata.timer.count != tdata.timer.repeatCount) {
1442					tdata.timer.start(tdata.delay);
1443				}
1444			}
1445		};
1446		// pause the timer and use a per slide delay
1447		tdata.timer.pause();
1448		var $cur = tdata.faces.$listTiles.filter(".active"),
1449            idx = tdata.faces.$listTiles.index($cur),
1450            goTo = tdata.currentIndex,
1451            eq = goTo != idx ? goTo : idx,
1452            nxtIdx = eq + 1 >= tdata.faces.$listTiles.length ? 0 : eq + 1,
1453            sdata = tdata.listData[nxtIdx];
1454		if (idx == nxtIdx) {
1455			aniData = null;
1456			$cur = null;
1457			return;
1458		}
1459		// get temp values passed in from data methods
1460		var animationDirection;
1461		if (typeof (tdata.tempValues.animationDirection) === "string" && tdata.tempValues.animationDirection.length > 0)
1462			animationDirection = tdata.tempValues.animationDirection;
1463		else if (typeof (sdata.animationDirection) === "string" && sdata.animationDirection.length > 0) {
1464			animationDirection = sdata.animationDirection;
1465		} else
1466			animationDirection = tdata.animationDirection;
1467		// the temp value for animation direction is not used in slide so i'm setting it to null
1468		tdata.tempValues.animationDirection = null;
1469		var direction;
1470		if (typeof (tdata.tempValues.direction) === "string" && tdata.tempValues.direction.length > 0) {
1471			direction = tdata.tempValues.direction;
1472		} else if (typeof (sdata.direction) === "string" && sdata.direction.length > 0) {
1473			direction = sdata.direction;
1474			tdata.tempValues.direction = direction;
1475		} else {
1476			direction = tdata.direction;
1477		}
1478		var $nxt = tdata.faces.$listTiles.eq(nxtIdx),
1479            start = tdata.animationStarting.call($tile[0], tdata, $cur, $nxt);
1480		if (typeof (start) != "undefined" && start == false) {
1481			resumeTimer();
1482			return;
1483		}
1484		tdata.loopCount = tdata.loopCount + 1;
1485		var nxtCss = helperMethods.appendStyleProperties({}, ['transition-duration'], ['0s']),
1486            vertical = direction === "vertical",
1487            translateTo;
1488		if (animationDirection === "backward") {
1489			if (!tdata.useTranslate || !metrojs.capabilities.canTransition) {
1490				if (vertical) {
1491					nxtCss.top = "-100%";
1492					nxtCss.left = "0%";
1493				} else {
1494					nxtCss.top = "0%";
1495					nxtCss.left = "-100%";
1496				}
1497				tdata.stops = ['100%'];
1498			} else {
1499				translateTo = vertical ? "translate(0%, -100%)" : "translate(-100%, 0%)";
1500				nxtCss = helperMethods.appendStyleProperties(nxtCss, ["transform"], [translateTo + " translateZ(0)"]);
1501				tdata.stops = ['100%'];
1502			}
1503			tdata.faces.$front = $cur;
1504			tdata.faces.$back = $nxt;
1505
1506		} else {
1507			if (!tdata.useTranslate || !metrojs.capabilities.canTransition) {
1508				if (vertical) {
1509					nxtCss.top = "100%";
1510					nxtCss.left = "0%";
1511				} else {
1512					nxtCss.top = "0%";
1513					nxtCss.left = "100%";
1514				}
1515			} else {
1516				translateTo = vertical ? "translate(0%, 100%)" : "translate(100%, 0%)";
1517				nxtCss = helperMethods.appendStyleProperties(nxtCss, ["transform"], [translateTo + " translateZ(0)"]);
1518			}
1519			tdata.faces.$front = $nxt;
1520			tdata.faces.$back = $cur;
1521			tdata.stops = ['0%'];
1522		}
1523		$nxt.css(nxtCss);
1524		// the timeout wrapper gives the css call above enough time to finish in case we dynamically set the direction
1525		window.setTimeout(function () {
1526			$cur.removeClass("active");
1527			$nxt.addClass("active");
1528			privMethods.slide($tile, count, tdata, 0, function () {
1529				tdata.currentIndex = nxtIdx;
1530				aniData = null;
1531				$cur = null;
1532				$nxt = null;
1533				resumeTimer();
1534			});
1535		}, 150);
1536
1537	},
1538	flip: function ($tile, count, data, callback) {
1539		var aniData = $tile.data("metrojs.tile");
1540		if (typeof (aniData) !== "undefined" && aniData.animating == true) {
1541			aniData = null;
1542			return;
1543		}
1544		var tdata = typeof (data) === "object" ? data : $tile.data("LiveTile");
1545		if (typeof (tdata) === "undefined") {
1546			throwError($.fn.liveTile.defaults.rebindMessage);
1547			return;
1548		}
1549		var $front, $back, direction, deg, rotateDir, css,
1550            raiseEvt = typeof (callback) === "undefined",
1551            index = 0,
1552            isReversed,  // the count starts at 1
1553            resumeTimer = function () {
1554            	// if the tile should run again start the timer back with the current delay
1555            	if (tdata.timer.repeatCount > 0 || tdata.timer.repeatCount == -1) {
1556            		if (tdata.timer.count != tdata.timer.repeatCount) {
1557            			tdata.timer.start(tdata.delay);
1558            		}
1559            	}
1560            };
1561		// the timer is only paused if animationComplete is fired
1562		if (raiseEvt) {
1563			tdata.timer.pause();
1564			var loopCount = tdata.loopCount + 1;
1565			isReversed = loopCount % 2 === 0;
1566			tdata.isReversed = isReversed;
1567			$front = tdata.faces.$front;
1568			$back = tdata.faces.$back;
1569			var args = isReversed ? [tdata, $back, $front] : [tdata, $front, $back];
1570			var start = tdata.animationStarting.apply($tile[0], args);
1571			if (typeof (start) != "undefined" && start == false) {
1572				resumeTimer();
1573				return;
1574			}
1575			direction = tdata.direction;
1576			height = tdata.height;
1577			width = tdata.width;
1578			margin = tdata.margin;
1579			tdata.loopCount = loopCount;
1580		} else {
1581			isReversed = count % 2 === 0;
1582			index = aniData.index;
1583			$front = tdata.listData[index].faces.$front;
1584			$back = tdata.listData[index].faces.$back;
1585			tdata.listData[index].isReversed = isReversed;
1586			direction = tdata.listData[index].direction;
1587			height = tdata.listData[index].height;
1588			width = tdata.listData[index].width;
1589			margin = tdata.listData[index].margin;
1590		}
1591
1592		if (metrojs.capabilities.canFlip3d && tdata.useHardwareAccel) { // Hardware accelerated :)
1593			deg = !isReversed ? "180deg" : "360deg";
1594			rotateDir = direction === "vertical" ? "rotateX(" + deg + ")" : "rotateY(" + deg + ")";
1595			css = helperMethods.appendStyleProperties({}, ["transform", "transition"], [rotateDir, "all " + tdata.speed + "ms " + tdata.haTransFunc + " 0s"]);
1596			var bDeg = !isReversed ? "360deg" : "540deg";
1597			var bRotateDir = direction === "vertical" ? "rotateX(" + bDeg + ")" : "rotateY(" + bDeg + ")";
1598			var bCss = helperMethods.appendStyleProperties({}, ["transform", "transition"], [bRotateDir, "all " + tdata.speed + "ms " + tdata.haTransFunc + " 0s"]);
1599			$front.css(css);
1600			$back.css(bCss);
1601
1602			var action = function () {
1603				aniData.animating = false;
1604				var resetDir, newCss;
1605				if (!isReversed) {
1606					privMethods.runContenModules(tdata, $back, $front, index);
1607					if (raiseEvt) {
1608						resumeTimer();
1609						tdata.animationComplete.call($tile[0], tdata, $back, $front);
1610					} else
1611						callback(tdata, $back, $front);
1612				} else {
1613					resetDir = direction === "vertical" ? "rotateX(0deg)" : "rotateY(0deg)";
1614					newCss = helperMethods.appendStyleProperties({}, ["transform", "transition"], [resetDir, "all 0s " + tdata.haTransFunc + " 0s"]);
1615					$front.css(newCss);
1616					//call content modules
1617					privMethods.runContenModules(tdata, $front, $back, index);
1618					if (raiseEvt) {
1619						resumeTimer();
1620						tdata.animationComplete.call($tile[0], tdata, $front, $back);
1621					} else
1622						callback(tdata, $front, $back);
1623					$front = null;
1624					$back = null;
1625					tdata = null;
1626					aniData = null;
1627				}
1628			};
1629			if (tdata.mode === "flip-list") {
1630				window.clearTimeout(tdata.listData[index].completeTimeout);
1631				tdata.listData[index].completeTimeout = window.setTimeout(action, tdata.speed);
1632			} else {
1633				window.clearTimeout(tdata.completeTimeout);
1634				tdata.completeTimeout = window.setTimeout(action, tdata.speed);
1635			}
1636		} else { // not Hardware accelerated :(
1637			var speed = tdata.speed / 2;
1638			var hideCss = (direction === "vertical") ?
1639						{ height: '0px', width: '100%', marginTop: margin + 'px', opacity: tdata.noHAflipOpacity } :
1640						{ height: '100%', width: '0px', marginLeft: margin + 'px', opacity: tdata.noHAflipOpacity };
1641			var showCss = (direction === "vertical") ?
1642                        { height: '100%', width: '100%', marginTop: '0px', opacity: '1' } :
1643                        { height: '100%', width: '100%', marginLeft: '0px', opacity: '1' };
1644			var noHaAction;
1645			if (!isReversed) {
1646				aniData.animating = true;
1647				$front.stop().animate(hideCss, { duration: speed });
1648				noHaAction = function () {
1649					aniData.animating = false;
1650					$back.stop().animate(showCss, {
1651						duration: speed,
1652						complete: function () {
1653							privMethods.runContenModules(tdata, $back, $front, index);
1654							if (raiseEvt) {
1655								resumeTimer();
1656								tdata.animationComplete.call($tile[0], tdata, $back, $front);
1657							} else
1658								callback(tdata, $back, $front);
1659							$front = null;
1660							$back = null;
1661							tdata = null;
1662							aniData = null;
1663						}
1664					});
1665				};
1666				if (tdata.mode === "flip-list") {
1667					window.clearTimeout(tdata.listData[aniData.index].completeTimeout);
1668					tdata.listData[aniData.index].completeTimeout = window.setTimeout(noHaAction, speed);
1669				} else {
1670					window.clearTimeout(tdata.completeTimeout);
1671					tdata.completeTimeout = window.setTimeout(noHaAction, speed);
1672				}
1673			} else {
1674				aniData.animating = true;
1675				$back.stop().animate(hideCss, { duration: speed });
1676				noHaAction = function () {
1677					aniData.animating = false;
1678					$front.stop().animate(showCss, {
1679						duration: speed,
1680						complete: function () {
1681							privMethods.runContenModules(tdata, $front, $back, index);
1682							if (raiseEvt) {
1683								resumeTimer();
1684								tdata.animationComplete.call($tile[0], tdata, $front, $back);
1685							} else
1686								callback(tdata, $front, $back);
1687							aniData = null;
1688							$front = null;
1689							$back = null;
1690						}
1691					});
1692				};
1693				if (tdata.mode === "flip-list") {
1694					window.clearTimeout(tdata.listData[aniData.index].completeTimeout);
1695					tdata.listData[aniData.index].completeTimeout = window.setTimeout(noHaAction, speed);
1696				} else {
1697					window.clearTimeout(tdata.completeTimeout);
1698					tdata.completeTimeout = window.setTimeout(noHaAction, speed);
1699				}
1700			}
1701
1702		}
1703	},
1704	flipList: function ($tile, count) {
1705		var tdata = $tile.data("LiveTile"),
1706            maxDelay = tdata.speed,
1707            triggered = false,
1708            resumeTimer = function () {
1709            	if (tdata.timer.repeatCount > 0 || tdata.timer.repeatCount == -1) {
1710            		if (tdata.timer.count != tdata.timer.repeatCount) {
1711            			tdata.timer.start(tdata.delay);
1712            		}
1713            	}
1714            };
1715		if (typeof (tdata) === "undefined") {
1716			throwError($.fn.liveTile.defaults.rebindMessage);
1717			return;
1718		}
1719		tdata.timer.pause();
1720		var start = tdata.animationStarting.call($tile[0], tdata, null, null);
1721		if (typeof (start) != "undefined" && start == false) {
1722			resumeTimer();
1723			return;
1724		}
1725		tdata.loopCount = tdata.loopCount + 1;
1726		tdata.faces.$listTiles.each(function (idx, ele) {
1727			var $li = $(ele),
1728                ldata = $li.data("metrojs.tile"),
1729                tDelay = tdata.triggerDelay(idx),
1730                triggerDelay = tdata.speed + Math.max(tDelay, 0),
1731                trigger = tdata.alwaysTrigger;
1732			if (!trigger)
1733				trigger = (Math.random() * 351) > 150 ? true : false;
1734			if (trigger) {
1735				triggered = true;
1736				maxDelay = Math.max(triggerDelay + tdata.speed, maxDelay);
1737				window.clearTimeout(ldata.flCompleteTimeout);
1738				ldata.flCompleteTimeout = window.setTimeout(function () {
1739					// call the flip method with the merged data, but dont fire animationComplete
1740					privMethods.flip($li, ldata.count, tdata, function (data) {
1741						ldata.count++;
1742						if (ldata.count >= MAX_LOOP_COUNT)
1743							ldata.count = 1;
1744						$li = null;
1745						ldata = null;
1746					});
1747				}, triggerDelay);
1748			}
1749		});
1750		if (triggered) {
1751			window.clearTimeout(tdata.flCompleteTimeout);
1752			tdata.flCompleteTimeout = window.setTimeout(function () {
1753				privMethods.runContenModules(tdata, null, null, -1);
1754				tdata.animationComplete.call($tile[0], tdata, null, null);
1755				resumeTimer();
1756			}, maxDelay + tdata.speed); // add some padding to make sure the final callback finished
1757
1758		}
1759	}
1760};
1761
1762
1763// methods that can be called more universally
1764var helperMethods = {
1765	stylePrefixes: 'Webkit Moz O ms Khtml '.split(' '),
1766	domPrefixes: '-webkit- -moz- -o- -ms- -khtml- '.split(' '),
1767	browserPrefix: null,
1768	// a method to append css3 properties for each browser
1769	// note: values are identical for each property
1770	appendStyleProperties: function (obj, names, values) {
1771		for (var i = 0; i <= names.length - 1; i++) {
1772			obj[$.trim(this.browserPrefix + names[i])] = values[i];
1773			obj[$.trim(names[i])] = values[i];
1774		}
1775		return obj;
1776	},
1777	applyStyleValue: function (obj, name, value) {
1778		obj[$.trim(this.browserPrefix + name)] = value;
1779		obj[name] = value;
1780		return obj;
1781	},
1782	getBrowserPrefix: function () {
1783		if (this.browserPrefix == null) {
1784			var prefix = "";
1785			for (var i = 0; i <= this.domPrefixes.length - 1; i++) {
1786				if (typeof (document.body.style[this.domPrefixes[i] + "transform"]) != "undefined")
1787					prefix = this.domPrefixes[i];
1788			}
1789			return this.browserPrefix = prefix;
1790		}
1791		return this.browserPrefix;
1792	},
1793	//a shuffle method to provide more randomness than sort
1794	//credit: http://javascript.about.com/library/blshuffle.htm
1795	//note: avoiding prototype for sharepoint compatability
1796	shuffleArray: function (array) {
1797		var s = [];
1798		while (array.length) s.push(array.splice(Math.random() * array.length, 1));
1799		while (s.length) array.push(s.pop());
1800		return array;
1801	}
1802};
1803
1804var defaultModules = {
1805	moduleName: 'custom',
1806	customSwap: {
1807		data: {
1808			customDoSwapFront: function () { return false; },
1809			customDoSwapBack: function () { return false; },
1810			customGetContent: function (tdata, $front, $back, index) { return null; }
1811		},
1812		initData: function (tdata, $ele) {
1813			var swapData = {};
1814			swapData.doSwapFront = $.inArray('custom', tdata.swapFront) > -1 && tdata.customDoSwapFront();
1815			swapData.doSwapBack = $.inArray('custom', tdata.swapBack) > -1 && tdata.customDoSwapBack();
1816			if (typeof (tdata.customSwap) !== "undefined")
1817				tdata.customSwap = $.extend(swapData, tdata.customSwap);
1818			else
1819				tdata.customSwap = swapData;
1820		},
1821		action: function (tdata, $front, $back, index) {
1822
1823		}
1824	},
1825	htmlSwap: {
1826		moduleName: 'html',
1827		data: { // public data for the swap module                
1828			frontContent: [],                       // a list of html to use for the front
1829			frontIsRandom: true,                    // should html be chosen at random or in order                
1830			frontIsInGrid: false,                   // only chooses one item for each iteration in flip-list                
1831			backContent: [],                        // a list of html to use for the back
1832			backIsRandom: true,                     // should html be chosen at random or in order                
1833			backIsInGrid: false                     // only chooses one item for each iteration in flip-list                
1834		},
1835		initData: function (tdata, $ele) {
1836			var swapData = { // private data for the swap module
1837				backBag: [],
1838				backIndex: 0,
1839				backStaticIndex: 0,
1840				backStaticRndm: -1,
1841				prevBackIndex: -1,
1842				frontBag: [],
1843				frontIndex: 0,
1844				frontStaticIndex: 0,
1845				frontStaticRndm: -1,
1846				prevFrontIndex: -1
1847			};
1848			if (!tdata.ignoreDataAttributes) {
1849				swapData.frontIsRandom = privMethods.getDataOrDefault($ele, "front-israndom", tdata.frontIsRandom);
1850				swapData.frontIsInGrid = privMethods.getDataOrDefault($ele, "front-isingrid", tdata.frontIsInGrid);
1851				swapData.backIsRandom = privMethods.getDataOrDefault($ele, "back-israndom", tdata.backIsRandom);
1852				swapData.backIsInGrid = privMethods.getDataOrDefault($ele, "back-isingrid", tdata.backIsInGrid);
1853			} else {
1854				swapData.frontIsRandom = tdata.frontIsRandom;
1855				swapData.frontIsInGrid = tdata.frontIsInGrid;
1856				swapData.backIsRandom = tdata.backIsRandom;
1857				swapData.backIsInGrid = tdata.backIsInGrid;
1858			}
1859			swapData.doSwapFront = $.inArray('html', tdata.swapFront) > -1 && (tdata.frontContent instanceof Array) && tdata.frontContent.length > 0;
1860			swapData.doSwapBack = $.inArray('html', tdata.swapBack) > -1 && (tdata.backContent instanceof Array) && tdata.backContent.length > 0;
1861			if (typeof (tdata.htmlSwap) !== "undefined")
1862				tdata.htmlSwap = $.extend(swapData, tdata.htmlSwap);
1863			else
1864				tdata.htmlSwap = swapData;
1865			if (tdata.htmlSwap.doSwapFront) {
1866				tdata.htmlSwap.frontBag = this.prepBag(tdata.htmlSwap.frontBag, tdata.frontContent, tdata.htmlSwap.prevFrontIndex);
1867				tdata.htmlSwap.frontStaticRndm = tdata.htmlSwap.frontBag.pop();
1868			}
1869			if (tdata.htmlSwap.doSwapBack) {
1870				tdata.htmlSwap.backBag = this.prepBag(tdata.htmlSwap.backBag, tdata.backContent, tdata.htmlSwap.prevBackIndex);
1871				tdata.htmlSwap.backStaticRndm = tdata.htmlSwap.backBag.pop();
1872			}
1873		},
1874		prepBag: function (bag, content, prevIdx) {
1875			bag = bag || [];
1876			var bagCount = 0;
1877			for (var i = 0; i < content.length; i++) {
1878				//make sure there's not an immediate repeat
1879				if (i != prevIdx || bag.length === 1) {
1880					bag[bagCount] = i;
1881					bagCount++;
1882				}
1883			}
1884			return helperMethods.shuffleArray(bag);
1885		},
1886		getFrontSwapIndex: function (tdata) {
1887			var idx = 0;
1888			if (!tdata.htmlSwap.frontIsRandom) {
1889				idx = tdata.htmlSwap.frontIsInGrid ? tdata.htmlSwap.frontStaticIndex : tdata.htmlSwap.frontIndex;
1890			} else {
1891				if (tdata.htmlSwap.frontBag.length === 0) {
1892					tdata.htmlSwap.frontBag = this.prepBag(tdata.htmlSwap.frontBag, tdata.frontContent, tdata.htmlSwap.prevFrontIndex);
1893				}
1894				if (tdata.htmlSwap.frontIsInGrid) {
1895					idx = tdata.htmlSwap.frontStaticRndm;
1896				} else {
1897					idx = tdata.htmlSwap.frontBag.pop();
1898				}
1899			}
1900			return idx;
1901		},
1902		getBackSwapIndex: function (tdata) {
1903			var idx = 0;
1904			if (!tdata.htmlSwap.backIsRandom) {
1905				idx = tdata.htmlSwap.backIsInGrid ? tdata.htmlSwap.backStaticIndex : tdata.htmlSwap.backIndex;
1906			} else {
1907				if (tdata.htmlSwap.backBag.length === 0) {
1908					tdata.htmlSwap.backBag = this.prepBag(tdata.htmlSwap.backBag, tdata.backContent, tdata.htmlSwap.prevBackIndex);
1909				}
1910				if (tdata.htmlSwap.backIsInGrid) {
1911					idx = tdata.htmlSwap.backStaticRndm;
1912				} else {
1913					idx = tdata.htmlSwap.backBag.pop();
1914				}
1915			}
1916			return idx;
1917		},
1918		action: function (tdata, $front, $back, index) {
1919			if (!tdata.htmlSwap.doSwapFront && !tdata.htmlSwap.doSwapBack)
1920				return;
1921			var isList = tdata.mode === "flip-list";
1922			var swapIndex = 0;
1923			var isReversed = isList ? tdata.listData[Math.max(index, 0)].isReversed : tdata.isReversed;
1924			if (isList && index == -1) {
1925				// flip list completed
1926				if (!isReversed) {
1927					if (tdata.htmlSwap.doSwapFront) {
1928						// update the random value for grid mode
1929						if (tdata.htmlSwap.frontBag.length === 0)
1930							tdata.htmlSwap.frontBag = this.prepBag(tdata.htmlSwap.frontBag, tdata.frontContent, tdata.htmlSwap.frontStaticRndm);
1931						tdata.htmlSwap.frontStaticRndm = tdata.htmlSwap.frontBag.pop();
1932						// update the static index
1933						tdata.htmlSwap.frontStaticIndex++;
1934						if (tdata.htmlSwap.frontStaticIndex >= tdata.frontContent.length)
1935							tdata.htmlSwap.frontStaticIndex = 0;
1936					}
1937				} else {
1938					if (tdata.htmlSwap.doSwapBack) {
1939						// update the random value for grid mode
1940						if (tdata.htmlSwap.backBag.length === 0)
1941							tdata.htmlSwap.backBag = this.prepBag(tdata.htmlSwap.backBag, tdata.backContent, tdata.htmlSwap.backStaticRndm);
1942						tdata.htmlSwap.backStaticRndm = tdata.htmlSwap.backBag.pop();
1943						// update the static index
1944						tdata.htmlSwap.backStaticIndex++;
1945						if (tdata.htmlSwap.backStaticIndex >= tdata.backContent.length)
1946							tdata.htmlSwap.backStaticIndex = 0;
1947					}
1948				}
1949				return;
1950			}
1951			if (!isReversed) {
1952				if (!tdata.htmlSwap.doSwapFront)
1953					return;
1954				swapIndex = this.getFrontSwapIndex(tdata);
1955				tdata.htmlSwap.prevFrontIndex = swapIndex;
1956				if (tdata.mode === "slide") {
1957					if (!tdata.startNow)
1958						$front.html(tdata.frontContent[swapIndex]);
1959					else
1960						$back.html(tdata.frontContent[swapIndex]);
1961				} else
1962					$back.html(tdata.frontContent[swapIndex]);
1963				// increment the front index to get the next item from the list
1964				tdata.htmlSwap.frontIndex++;
1965				if (tdata.htmlSwap.frontIndex >= tdata.frontContent.length)
1966					tdata.htmlSwap.frontIndex = 0;
1967				if (!isList) {
1968					// increment the static index if we're not in list mode
1969					tdata.htmlSwap.frontStaticIndex++;
1970					if (tdata.htmlSwap.frontStaticIndex >= tdata.frontContent.length)
1971						tdata.htmlSwap.frontStaticIndex = 0;
1972				} else {
1973					// flip list
1974				}
1975			} else {
1976				if (!tdata.htmlSwap.doSwapBack)
1977					return;
1978				swapIndex = this.getBackSwapIndex(tdata);
1979				tdata.htmlSwap.prevBackIndex = swapIndex;
1980				$back.html(tdata.backContent[tdata.htmlSwap.backIndex]);
1981				tdata.htmlSwap.backIndex++;
1982				if (tdata.htmlSwap.backIndex >= tdata.backContent.length)
1983					tdata.htmlSwap.backIndex = 0;
1984				if (!isList) {
1985					tdata.htmlSwap.backStaticIndex++;
1986					if (tdata.htmlSwap.backStaticIndex >= tdata.backContent.length)
1987						tdata.htmlSwap.backStaticIndex = 0;
1988				} else {
1989					// flip list
1990				}
1991			}
1992		}
1993	},
1994	imageSwap: {
1995		moduleName: 'image',
1996		data: {
1997			preloadImages: false,
1998			imageCssSelector: '>img,>a>img',        // the selector used to choose a an image to apply a src or background to
1999			fadeSwap: false,                        // fade the image before swapping
2000			frontImages: [],                        // a list of images to use for the front
2001			frontIsRandom: true,                    // should images be chosen at random or in order
2002			frontIsBackgroundImage: false,          // set the src attribute or css background-image property
2003			frontIsInGrid: false,                   // only chooses one item for each iteration in flip-list
2004			backImages: null,                       // a list of images to use for the back
2005			backIsRandom: true,                     // should images be chosen at random or in order
2006			backIsBackgroundImage: false,           // set the src attribute or css background-image property
2007			backIsInGrid: false                     // only chooses one item for each iteration in flip-list                
2008		},
2009		initData: function (tdata, $ele) {
2010			var swapData = {
2011				backBag: [],
2012				backIndex: 0,
2013				backStaticIndex: 0,
2014				backStaticRndm: -1,
2015				frontBag: [],
2016				frontIndex: 0,
2017				frontStaticIndex: 0,
2018				frontStaticRndm: -1,
2019				prevBackIndex: -1,
2020				prevFrontIndex: -1
2021			}, useData = tdata.ignoreDataAttributes;
2022			if (useData) {
2023				swapData.imageCssSelector = privMethods.getDataOrDefault($ele, "image-css", tdata.imageCssSelector);
2024				swapData.fadeSwap = privMethods.getDataOrDefault($ele, "fadeswap", tdata.fadeSwap);
2025				swapData.frontIsRandom = privMethods.getDataOrDefault($ele, "front-israndom", tdata.frontIsRandom);
2026				swapData.frontIsInGrid = privMethods.getDataOrDefault($ele, "front-isingrid", tdata.frontIsInGrid);
2027				swapData.frontIsBackgroundImage = privMethods.getDataOrDefault($ele, "front-isbg", tdata.frontIsBackgroundImage);
2028				swapData.backIsRandom = privMethods.getDataOrDefault($ele, "back-israndom", tdata.backIsRandom);
2029				swapData.backIsInGrid = privMethods.getDataOrDefault($ele, "back-isingrid", tdata.backIsInGrid);
2030				swapData.backIsBackgroundImage = privMethods.getDataOrDefault($ele, "back-isbg", tdata.backIsBackgroundImage);
2031				swapData.doSwapFront = $.inArray('image', tdata.swapFront) > -1 && (tdata.frontImages instanceof Array) && tdata.frontImages.length > 0;
2032				swapData.doSwapBack = $.inArray('image', tdata.swapBack) > -1 && (tdata.backImages instanceof Array) && tdata.backImages.length > 0;
2033				swapData.alwaysSwapFront = privMethods.getDataOrDefault($ele, "front-alwaysswap", tdata.alwaysSwapFront);
2034				swapData.alwaysSwapBack = privMethods.getDataOrDefault($ele, "back-alwaysswap", tdata.alwaysSwapBack);
2035			} else {
2036				swapData.imageCssSelector = tdata.imageCssSelector;
2037				swapData.fadeSwap = tdata.fadeSwap;
2038				swapData.frontIsRandom = tdata.frontIsRandom;
2039				swapData.frontIsInGrid = tdata.frontIsInGrid;
2040				swapData.frontIsBackgroundImage = tdata.frontIsBackgroundImage;
2041				swapData.backIsRandom = tdata.backIsRandom;
2042				swapData.backIsInGrid = tdata.backIsInGrid;
2043				swapData.backIsBackgroundImage = tdata.backIsBackgroundImage;
2044				swapData.doSwapFront = $.inArray('image', tdata.swapFront) > -1 && (tdata.frontImages instanceof Array) && tdata.frontImages.length > 0;
2045				swapData.doSwapBack = $.inArray('image', tdata.swapBack) > -1 && (tdata.backImages instanceof Array) && tdata.backImages.length > 0;
2046				swapData.alwaysSwapFront = tdata.alwaysSwapFront;
2047				swapData.alwaysSwapBack = tdata.alwaysSwapBack;
2048			}
2049			if (typeof (tdata.imgSwap) !== "undefined")
2050				tdata.imgSwap = $.extend(swapData, tdata.imgSwap);
2051			else
2052				tdata.imgSwap = swapData;
2053			if (tdata.imgSwap.doSwapFront) {
2054				tdata.imgSwap.frontBag = this.prepBag(tdata.imgSwap.frontBag, tdata.frontImages, tdata.imgSwap.prevFrontIndex);
2055				tdata.imgSwap.frontStaticRndm = tdata.imgSwap.frontBag.pop();
2056				if (tdata.preloadImages)
2057					$(tdata.frontImages).metrojs.preloadImages(function () { });
2058			}
2059			if (tdata.imgSwap.doSwapBack) {
2060				tdata.imgSwap.backBag = this.prepBag(tdata.imgSwap.backBag, tdata.backImages, tdata.imgSwap.prevBackIndex);
2061				tdata.imgSwap.backStaticRndm = tdata.imgSwap.backBag.pop();
2062				if (tdata.preloadImages)
2063					$(tdata.backImages).metrojs.preloadImages(function () { });
2064			}
2065		},
2066		prepBag: function (bag, content, prevIdx) {
2067			bag = bag || [];
2068			var bagCount = 0;
2069			for (var i = 0; i < content.length; i++) {
2070				//make sure there's not an immediate repeat
2071				if (i != prevIdx || content.length === 1) {
2072					bag[bagCount] = i;
2073					bagCount++;
2074				}
2075			}
2076			return helperMethods.shuffleArray(bag);
2077		},
2078		getFrontSwapIndex: function (tdata) {
2079			var idx = 0;
2080			if (!tdata.imgSwap.frontIsRandom) {
2081				idx = tdata.imgSwap.frontIsInGrid ? tdata.imgSwap.frontStaticIndex : tdata.imgSwap.frontIndex;
2082			} else {
2083				if (tdata.imgSwap.frontBag.length === 0) {
2084					tdata.imgSwap.frontBag = this.prepBag(tdata.imgSwap.frontBag, tdata.frontImages, tdata.imgSwap.prevFrontIndex);
2085				}
2086				if (tdata.imgSwap.frontIsInGrid) {
2087					idx = tdata.imgSwap.frontStaticRndm;
2088				} else {
2089					idx = tdata.imgSwap.frontBag.pop();
2090				}
2091			}
2092			return idx;
2093		},
2094		getBackSwapIndex: function (tdata) {
2095			var idx = 0;
2096			if (!tdata.imgSwap.backIsRandom) {
2097				idx = tdata.imgSwap.backIsInGrid ? tdata.imgSwap.backStaticIndex : tdata.imgSwap.backIndex;
2098			} else {
2099				if (tdata.imgSwap.backBag.length === 0) {
2100					tdata.imgSwap.backBag = this.prepBag(tdata.imgSwap.backBag, tdata.backImages, tdata.imgSwap.prevBackIndex);
2101				}
2102				if (tdata.imgSwap.backIsInGrid) {
2103					idx = tdata.imgSwap.backStaticRndm;
2104				} else {
2105					idx = tdata.imgSwap.backBag.pop();
2106				}
2107			}
2108			return idx;
2109		},
2110		setImageProperties: function ($img, image, isBackground) {
2111			var css = {}, // css object to apply
2112                attr = {}; // attribute values to apply
2113			// get image source
2114			if (typeof (image.src) !== 'undefined') {
2115				if (!isBackground)
2116					attr.src = image.src;
2117				else
2118					css.backgroundImage = "url('" + image.src + "')";
2119			}
2120			// get alt text
2121			if (typeof (image.alt) !== 'undefined')
2122				attr.alt = image.alt;
2123			// set css
2124			if (typeof (image.css) === 'object')
2125				$img.css($.extend(css, image.css));
2126			else
2127				$img.css(css);
2128			// set attributes
2129			if (typeof (image.attr) === 'object')
2130				$img.attr($.extend(attr, image.attr));
2131			else
2132				$img.attr(attr);
2133		},
2134		action: function (tdata, $front, $back, index) {
2135			if (!tdata.imgSwap.doSwapFront && !tdata.imgSwap.doSwapBack)
2136				return;
2137			var isList = tdata.mode === "flip-list",
2138                isSlide = tdata.mode == "slide",
2139                swapIndex = 0,
2140                isReversed = isList ? tdata.listData[Math.max(index, 0)].isReversed : tdata.isReversed;
2141			if (isList && index == -1) {
2142				// flip list completed
2143				if (tdata.alwaysSwapFront || !isReversed) {
2144					if (tdata.imgSwap.doSwapFront) {
2145						// update the random value for grid mode
2146						if (tdata.imgSwap.frontBag.length === 0)
2147							tdata.imgSwap.frontBag = this.prepBag(tdata.imgSwap.frontBag, tdata.frontImages, tdata.imgSwap.frontStaticRndm);
2148						tdata.imgSwap.frontStaticRndm = tdata.imgSwap.frontBag.pop();
2149						// update the static index
2150						tdata.imgSwap.frontStaticIndex++;
2151						if (tdata.imgSwap.frontStaticIndex >= tdata.frontImages.length)
2152							tdata.imgSwap.frontStaticIndex = 0;
2153					}
2154				}
2155				if (tdata.alwaysSwapBack || isReversed) {
2156					if (tdata.imgSwap.doSwapBack) {
2157						// update the random value for grid mode
2158						if (tdata.imgSwap.backBag.length === 0)
2159							tdata.imgSwap.backBag = this.prepBag(tdata.imgSwap.backBag, tdata.backImages, tdata.imgSwap.backStaticRndm);
2160						tdata.imgSwap.backStaticRndm = tdata.imgSwap.backBag.pop();
2161						// update the static index
2162						tdata.imgSwap.backStaticIndex++;
2163						if (tdata.imgSwap.backStaticIndex >= tdata.backImages.length)
2164							tdata.imgSwap.backStaticIndex = 0;
2165					}
2166				}
2167				return;
2168			}
2169			var $face, // face being swapped
2170                $img, // image to apply values
2171                image,// image object to hold properties
2172                swap; // wrapper for setimageProperties for fade
2173			if (tdata.alwaysSwapFront || !isReversed) {
2174				if (!tdata.imgSwap.doSwapFront)
2175					return;
2176				swapIndex = this.getFrontSwapIndex(tdata);
2177				tdata.imgSwap.prevFrontIndex = swapIndex;
2178				// slide mode has a static front and back face
2179				$face = (tdata.mode === "slide") ? $front : $back;
2180				$img = $face.find(tdata.imgSwap.imageCssSelector);
2181				image = typeof (tdata.frontImages[swapIndex]) === "object" ? tdata.frontImages[swapIndex] : { src: tdata.frontImages[swapIndex] };
2182				swap = function (callback) {
2183					// set src, alt, css and attribute values
2184					var isBg = tdata.imgSwap.frontIsBackgroundImage;
2185					if (typeof (callback) == "function") {
2186						if (isBg)
2187							window.setTimeout(callback, 100);
2188						else
2189							$img[0].onload = callback;
2190					}
2191					defaultModules.imageSwap.setImageProperties($img, image, isBg);
2192
2193				};
2194				if (tdata.fadeSwap) {
2195					$img.fadeOut(function () {
2196						swap(function () {
2197							$img.fadeIn();
2198						});
2199					});
2200				} else
2201					swap();
2202				// increment indexes
2203				tdata.imgSwap.frontIndex++;
2204				if (tdata.imgSwap.frontIndex >= tdata.frontImages.length)
2205					tdata.imgSwap.frontIndex = 0;
2206				if (!isList) {
2207					tdata.imgSwap.frontStaticIndex++;
2208					if (tdata.imgSwap.frontStaticIndex >= tdata.frontImages.length)
2209						tdata.imgSwap.frontStaticIndex = 0;
2210				} else {
2211
2212				}
2213			}
2214			if (tdata.alwaysSwapBack || isReversed) {
2215				if (!tdata.imgSwap.doSwapBack)
2216					return;
2217				// get the new index
2218				swapIndex = this.getBackSwapIndex(tdata);
2219				tdata.imgSwap.prevBackIndex = swapIndex;
2220				// use the $face var for consistency
2221				$face = $back;
2222				$img = $face.find(tdata.imgSwap.imageCssSelector);
2223				image = typeof (tdata.backImages[swapIndex]) === "object" ? tdata.backImages[swapIndex] : { src: tdata.backImages[swapIndex] };
2224				swap = function () {
2225					// set src, alt, css and attribute values
2226					defaultModules.imageSwap.setImageProperties($img, image, tdata.imgSwap.backIsBackgroundImage);
2227				};
2228				if (tdata.fadeSwap) {
2229					$img.fadeOut(function () {
2230						swap(function () {
2231							$img.fadeIn();
2232						});
2233					});
2234				} else
2235					swap();
2236				// increment indexes
2237				tdata.imgSwap.backIndex++;
2238				if (tdata.imgSwap.backIndex >= tdata.backImages.length)
2239					tdata.imgSwap.backIndex = 0;
2240				if (!isList) {
2241					tdata.imgSwap.backStaticIndex++;
2242					if (tdata.imgSwap.backStaticIndex >= tdata.backImages.length)
2243						tdata.imgSwap.backStaticIndex = 0;
2244				} else {
2245
2246				}
2247			}
2248		}
2249	}
2250};
2251
2252// object to maintain timer state
2253$.fn.metrojs.TileTimer = function (interval, callback, repeatCount) {
2254	this.timerId = null;                                                        // the id of the current timeout
2255	this.interval = interval;                                                   // the amount of time to wait between each action call
2256	this.action = callback;                                                     // the method that is fired on each tick
2257	this.count = 0;                                                             // the number of times the action has been fired
2258	this.repeatCount = typeof (repeatCount) === "undefined" ? 0 : repeatCount;   // the number of times the action will be fired        
2259	// call the action method after a delay and call start | stop based on repeat count
2260	this.start = function (delay) {
2261		window.clearTimeout(this.timerId);
2262		var t = this;
2263		this.timerId = window.setTimeout(function () {
2264			t.tick.call(t, interval);
2265		}, delay);
2266	};
2267
2268	this.tick = function (when) {
2269		this.action(this.count + 1);
2270		this.count++;
2271		// reset the loop count
2272		if (this.count >= MAX_LOOP_COUNT)
2273			this.count = 0;
2274		if (this.repeatCount > 0 || this.repeatCount == -1) {
2275			if (this.count != this.repeatCount) {
2276				this.start(when);
2277			} else
2278				this.stop();
2279		}
2280	}
2281	// clear the timer and reset the count
2282	this.stop = function () {
2283		this.timerId = window.clearTimeout(this.timerId);
2284		this.reset();
2285	};
2286
2287	this.resume = function () {
2288		if (this.repeatCount > 0 || this.repeatCount == -1) {
2289			if (this.count != this.repeatCount) {
2290				this.start(interval);
2291			}
2292		}
2293	};
2294
2295	// clear the timer but leave the count intact
2296	this.pause = function () {
2297		this.timerId = window.clearTimeout(this.timerId);
2298	};
2299
2300	// reset count
2301	this.reset = function () {
2302		this.count = 0;
2303	};
2304
2305	// reset count and timer
2306	this.restart = function (delay) {
2307		this.stop();
2308		this.start(delay);
2309	};
2310};
2311jQuery.fn.metrojs.theme = {
2312	loadDefaultTheme: function (stgs) {
2313		if (typeof (stgs) === "undefined" || stgs == null) {
2314			stgs = jQuery.fn.metrojs.theme.defaults;
2315		} else {
2316			var stg = jQuery.fn.metrojs.theme.defaults;
2317			jQuery.extend(stg, stgs);
2318			stgs = stg;
2319		}
2320		//get theme from local storage or set base theme
2321		var hasLocalStorage = typeof (window.localStorage) !== "undefined";
2322		var hasKeyAndValue = function (key) {
2323			return (typeof (window.localStorage[key]) !== "undefined" && window.localStorage[key] != null);
2324		};
2325		if (hasLocalStorage && (!hasKeyAndValue("Metro.JS.AccentColor") || !hasKeyAndValue("Metro.JS.BaseAcce
2325ntColor"))) {
2326			//base theme
2327			window.localStorage["Metro.JS.AccentColor"] = stgs.accentColor;
2328			window.localStorage["Metro.JS.BaseAccentColor"] = stgs.baseTheme;
2329			jQuery(stgs.accentCssSelector).addClass(stgs.accentColor).data("accent", stgs.accentColor);
2330			jQuery(stgs.baseThemeCssSelector).addClass(stgs.baseTheme);
2331			if (typeof (stgs.loaded) === "function")
2332				stgs.loaded(stgs.baseTheme, stgs.accentColor);
2333			//preload light theme image
2334			if (typeof (stgs.preloadAltBaseTheme) !== "undefined" && stgs.preloadAltBaseTheme)
2335				jQuery([(stgs.baseTheme == 'dark') ? stgs.metroLightUrl : stgs.metroDarkUrl]).metrojs.preloadImages(function () { });
2336		} else {
2337			if (hasLocalStorage) {
2338				stgs.accentColor = window.localStorage["Metro.JS.AccentColor"];
2339				stgs.baseTheme = window.localStorage["Metro.JS.BaseAccentColor"];
2340				jQuery(stgs.accentCssSelector).addClass(stgs.accentColor).data("accent", stgs.accentColor);
2341				jQuery(stgs.baseThemeCssSelector).addClass(stgs.baseTheme);
2342				if (typeof (stgs.loaded) === "function")
2343					stgs.loaded(stgs.baseTheme, stgs.accentColor);
2344			} else {
2345				jQuery(stgs.accentCssSelector).addClass(stgs.accentColor).data("accent", stgs.accentColor);
2346				jQuery(stgs.baseThemeCssSelector).addClass(stgs.baseTheme);
2347				if (typeof (stgs.loaded) === "function")
2348					stgs.loaded(stgs.baseTheme, stgs.accentColor);
2349				//preload light theme image
2350				if (typeof (stgs.preloadAltBaseTheme) !== "undefined" && stgs.preloadAltBaseTheme)
2351					jQuery([(stgs.baseTheme == 'dark') ? stgs.metroLightUrl : stgs.metroDarkUrl]).metrojs.preloadImages(function () { });
2352			}
2353		}
2354	},
2355	applyTheme: function (tColor, aColor, stgs) {
2356		if (typeof (stgs) === "undefined" || stgs == null) {
2357			stgs = jQuery.fn.metrojs.theme.defaults;
2358		} else {
2359			var stg = jQuery.fn.metrojs.theme.defaults;
2360			stgs = jQuery.extend({}, stg, stgs);
2361		}
2362
2363		if (typeof (tColor) !== "undefined" && tColor != null) {
2364			if (typeof (window.localStorage) !== "undefined") {
2365				window.localStorage["Metro.JS.BaseAccentColor"] = tColor;
2366			}
2367			var $theme = jQuery(stgs.baseThemeCssSelector);
2368			if ($theme.length > 0) {
2369				if (tColor == "dark")
2370					$theme.addClass("dark").removeClass("light");
2371				else if (tColor == "light")
2372					$theme.addClass("light").removeClass("dark");
2373			}
2374		}
2375		if (typeof (aColor) !== "undefined" && aColor != null) {
2376			if (typeof (window.localStorage) !== "undefined") {
2377				window.localStorage["Metro.JS.AccentColor"] = aColor;
2378			}
2379			var $accent = jQuery(stgs.accentCssSelector);
2380			if ($accent.length > 0) {
2381				var themeset = false;
2382				$accent.each(function () {
2383					jQuery(this).addClass(aColor);
2384					var dAccent = jQuery(this).data("accent");
2385					if (dAccent != aColor) {
2386						var cleanClass = jQuery(this).attr("class").replace(dAccent, "");
2387						cleanClass = cleanClass.replace(/(\s)+/, ' ');
2388						jQuery(this).attr("class", cleanClass);
2389						jQuery(this).data("accent", aColor);
2390						themeset = true;
2391					}
2392				});
2393				if (themeset && typeof (stgs.accentPicked) === "function")
2394					stgs.accentPicked(aColor);
2395			}
2396		}
2397	},
2398	appendAccentColors: function (stgs) {
2399		if (typeof (stgs) === "undefined" || stgs == null) {
2400			stgs = jQuery.fn.metrojs.theme.defaults;
2401		} else {
2402			var stg = jQuery.fn.metrojs.theme.defaults;
2403			stgs = jQuery.extend({}, stg, stgs);
2404		}
2405		var themeList = "";
2406		var themes = stgs.accentColors;
2407		var template = stgs.accentListTemplate;
2408		for (var i = 0; i < themes.length; i++) {
2409			themeList += template.replace(/\{0\}/g, themes[i]);
2410		}
2411		$(themeList).appendTo(stgs.accentListContainer);
2412	},
2413	appendBaseThemes: function (stgs) {
2414		if (typeof (stgs) === "undefined" || stgs == null) {
2415			stgs = jQuery.fn.metrojs.theme.defaults;
2416		} else {
2417			var stg = jQuery.fn.metrojs.theme.defaults;
2418			stgs = jQuery.extend({}, stg, stgs);
2419		}
2420		var themeList = "",
2421            themes = stgs.baseThemes,
2422            template = stgs.baseThemeListTemplate;
2423		for (var i = 0; i < themes.length; i++) {
2424			themeList += template.replace(/\{0\}/g, themes[i]);
2425		}
2426		$(themeList).appendTo(stgs.baseThemeListContainer);
2427	},
2428	// default options for theme
2429	defaults: {
2430		baseThemeCssSelector: 'body',                           // selector to place dark or light class after load or selection
2431		accentCssSelector: '.tiles',                            // selector to place accent color class after load or selection
2432		accentColor: 'blue',                                    // the default accent color. options are blue, brown, green, lime, magenta, mango, pink, purple, red, teal
2433		baseTheme: 'dark',                                      // the default theme color. options are dark, light
2434		accentColors: [
2435             'amber', 'blue', 'brown', 'cobalt', 'crimson', 'cyan',
2436             'magenta', 'lime', 'indigo', 'green', 'emerald',
2437             'mango', 'mauve', 'olive', 'orange', 'pink', 'red',
2438             'sienna', 'steel', 'teal', 'violet', 'yellow'
2439		],
2440		baseThemes: [
2441            'light',
2442            'dark'
2443		],
2444		accentListTemplate: "<li><a href='javascript:;' title='{0}' class='accent {0}'></a></li>", // template to generate accent options
2445		accentListContainer: "ul.theme-options,.theme-options>ul",                   // selector of container to append accents
2446		baseThemeListTemplate: "<li><a href='javascript:;' title='{0}' class='accent {0}'></a></li>", // template to generate accent options
2447		baseThemeListContainer: "ul.base-theme-options,.base-theme-options>ul"                    // selector of container to append accents
2448	}
2449};
2450
2451jQuery.fn.applicationBar = function (options) {    
2452    /* Setup the public options for the applicationBar  */
2453    var stgs = typeof (jQuery.fn.metrojs.theme) !== "undefined" ? jQuery.fn.metrojs.theme.defaults : {};
2454    jQuery.extend(stgs, jQuery.fn.applicationBar.defaults, options);
2455    if (typeof (jQuery.fn.metrojs.theme) != "undefined") {
2456        var theme = jQuery.fn.metrojs.theme;
2457        if (stgs.shouldApplyTheme) {         
2458            theme.loadDefaultTheme(stgs);
2459        }
2460        var themeContainer = stgs.accentListContainer.replace(",", " a,") + " a";
2461        var themeContainerClick = function () {
2462            var accent = jQuery(this).attr("class").replace("accent", "").replace(" ", "");
2463            theme.applyTheme(null, accent, stgs);
2464            if (typeof (stgs.accentPicked) == "function")
2465                stgs.accentPicked(accent);
2466        };
2467        var baseContainer = stgs.baseThemeListContainer.replace(","," a,") + " a";
2468        var baseContainerClick = function () {
2469            var accent = jQuery(this).attr("class").replace("accent", '').replace(' ', '');
2470            theme.applyTheme(accent, null, stgs);
2471            if (typeof (stgs.themePicked) == "function")
2472                stgs.themePicked(accent);
2473        };
2474        if (typeof ($.fn.on) === "function") {
2475            $(this).on("click.appBar", themeContainer, themeContainerClick);
2476            $(this).on("click.appBar", baseContainer, baseContainerClick);
2477        } else {
2478            $(themeContainer).live("click.appBar", themeContainerClick);
2479            $(baseContainer).live("click.appBar", baseContainerClick);
2480        }
2481    }
2482    //this should really only run once but we can support multiple application bars
2483    return $(this).each(function (idx, ele) {
2484    	var $this = $(ele),
2485            data = $.extend({}, stgs);
2486    	if(data.collapseHeight == "auto")
2487        	data.collapseHeight = $(this).outerHeight();
2488
2489        //unfortunately we have to sniff out mobile browsers because of the inconsistent implementation of position:fixed
2490        //most desktop methods return false positives on a mobile
2491        //todo: find/come up with a better fixed position test
2492        if (navigator.userAgent.match(/(Android|webOS|iPhone|iPod|BlackBerry|PIE|IEMobile)/i)) {
2493            // IEMobile10 supports position:fixed. This should cover up to IE20 or at least until fixed positioning gets sorted            
2494            // let iOS 5+ pass as well, hopefully by iOS 9 fixed pos will be standard :/
2495            if (!navigator.userAgent.match(/(IEMobile\/1)/i) && !navigator.userAgent.match(/(iPhone OS [56789])/i)) {
2496                $this.css({ position: 'absolute', bottom: '0px' });
2497            }
2498        }
2499        data.slideOpen = function () {
2500            if (!$this.hasClass("expanded"))
2501                data.animateAppBar(false);
2502        };
2503        data.slideClosed = function () {
2504            if ($this.hasClass("expanded"))
2505                data.animateAppBar(true);
2506        };
2507        data.animateAppBar = function (isExpanded) {
2508            var hgt = isExpanded ? data.collapseHeight : data.expandHeight;
2509            if (isExpanded)
2510                $this.removeClass("expanded");
2511            else
2512                if (!$this.hasClass("expanded"))
2513                    $this.addClass("expanded");
2514            $this.stop().animate({ height: hgt }, { duration: data.duration });
2515        };
2516        $this.data("ApplicationBar", data)
2517
2518        $this.find(stgs.handleSelector).click(function () {
2519            data.animateAppBar($this.hasClass("expanded"));
2520        });
2521
2522        if (data.bindKeyboard == true) {
2523            jQuery(document.documentElement).keyup(function (event) {
2524                // handle cursor keys
2525                if (event.keyCode == 38) {
2526                    // expand
2527                    if (event.target && event.target.tagName.match(/INPUT|TEXTAREA|SELECT/i) == null) {
2528                        if (!$this.hasClass("expanded")) {
2529                            data.animateAppBar(false);
2530                        }
2531                    }
2532                    
2533                } else if (event.keyCode == 40) {
2534                    // collapse
2535                    if (event.target && event.target.tagName.match(/INPUT|TEXTAREA|SELECT/i) == null) {
2536                        if ($this.hasClass("expanded")) {
2537                            data.animateAppBar(true);
2538                        }
2539                    }
2540                }
2541            });            
2542        }
2543    });
2544};
2545
2546// default options for applicationBar, the theme defaults are merged with this object when the applicationBar function is called
2547jQuery.fn.applicationBar.defaults = {
2548    applyTheme: true,                                       // should the theme be loaded from local storage and applied to the page
2549    themePicked: function (tColor) { },                     // called when a new theme is chosen. the chosen theme (dark | light)
2550    accentPicked: function (aColor) { },                    // called when a new accent is chosen. the chosen theme (blue, mango, purple, etc.)
2551    loaded: function (tColor, aColor) { },                  // called if applyTheme is true onload when a theme has been loaded from local storage or overridden by options
2552    duration: 300,                                          // how fast should animation be performed, in milliseconds
2553    expandHeight: "320px",                                  // height the application bar to expand to when opened
2554    collapseHeight: "auto",                                 // height the application bar will collapse back to when closed
2555    bindKeyboard: true,                                     // should up and down keys on keyborad be bound to the application bar
2556    handleSelector: "a.etc",
2557    metroLightUrl: 'images/metroIcons_light.jpg',  // the url for the metro light icons (only needed if preload 'preloadAltBaseTheme' is true)
2558    metroDarkUrl: 'images/metroIcons.jpg',         // the url for the metro dark icons (only needed if preload 'preloadAltBaseTheme' is true)
2559    preloadAltBaseTheme: false                             // should the applicationBar icons be pre loaded for the alternate theme to enable fast theme switching    
2560};
2561/* Preload Images */
2562// Usage: jQuery(['img1.jpg', { src: 'img2.jpg' }]).metrojs.preloadImages(function(){ ... });
2563// Callback function gets called after all images are preloaded
2564$.fn.metrojs.preloadImages = function (callback) {
2565        var checklist = $(this).toArray();
2566        var $img = $("<img style='display:none;' />").appendTo("body");
2567        $(this).each(function () {
2568                var src = this;
2569                if (typeof(this) == "object")
2570                        src = this.src;
2571                $img.attr({ src: src }).load(function() {
2572                        for (var i = 0; i < checklist.length; i++) {
2573                                if (checklist[i] == element) {
2574                                        checklist.splice(i, 1);
2575                                }
2576                        }
2577                        if (checklist.length == 0) {
2578                                callback();
2579                        }
2580                });
2581               
2582        });
2583    $img.remove();
2584};
2585// object used for compatibility checks
2586$.fn.metrojs.MetroModernizr = function (stgs) {
2587    if(typeof(stgs) === "undefined") {
2588                stgs = { useHardwareAccel: true, useModernizr: typeof(window.Modernizr) !== "undefined" };
2589        }
2590    this.isOldJQuery =  /^1\.[0123]/.test($.fn.jquery),
2591    this.isOldAndroid = (function(){
2592        try{
2593            var ua = navigator.userAgent;        
2594            if( ua.indexOf("Android") >= 0 )
2595            {
2596                var androidversion = parseFloat(ua.slice(ua.indexOf("Android")+8));
2597                if (androidversion < 2.3)
2598                    return true;
2599            }
2600        }catch(err){ $.error(err); }
2601        return false;
2602    })();
2603    this.canTransform = false;
2604    this.canTransition = false;
2605    this.canTransform3d = false;
2606    this.canAnimate = false;
2607    this.canTouch = false;
2608    this.canFlip3d = stgs.useHardwareAccel;
2609    if (stgs.useHardwareAccel == true) {
2610        if (stgs.useModernizr == false) {
2611            //determine if the browser supports the neccessary accelerated features
2612            if (typeof (window.MetroModernizr) !== "undefined") {
2613                this.canTransform = window.MetroModernizr.canTransform;
2614                this.canTransition = window.MetroModernizr.canTransition;
2615                this.canTransform3d = window.MetroModernizr.canTransform3d;
2616                this.canAnimate = window.MetroModernizr.canAnimate;
2617                this.canTouch = window.MetroModernizr.canTouch;
2618            } else {
2619                window.MetroModernizr = {};
2620                /***** check for browser capabilities credit: modernizr-1.7 http://modernizr.com/ *****/
2621                var mod = 'metromodernizr';
2622                var docElement = document.documentElement;
2623                var docHead = document.head || document.getElementsByTagName('head')[0];
2624                var modElem = document.createElement(mod);
2625                var m_style = modElem.style;
2626                var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
2627                var domPrefixes = 'Webkit Moz O ms Khtml'.split(' ');
2628                var test_props = function (props, callback) {
2629                    for (var i in props) {
2630                        if (m_style[props[i]] !== undefined && (!callback || callback(props[i], modElem))) {
2631                            return true;
2632                        }
2633                    }
2634                };
2635                var test_props_all = function (prop, callback) {
2636                    var uc_prop = prop.charAt(0).toUpperCase() + prop.substr(1),
2637                    props = (prop + ' ' + domPrefixes.join(uc_prop + ' ') + uc_prop).split(' ');
2638                    return !!test_props(props, callback);
2639                };
2640                var test_3d = function () {
2641                    var ret = !!test_props(['perspectiveProperty', 'WebkitPerspective', 'MozPerspective', 'OPerspective', 'msPerspective']);
2642                    if (ret && 'webkitPerspective' in docElement.style) {
2643                        // Webkit allows this media query to succeed only if the feature is enabled.
2644                        // '@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-ms-transform-3d),(-webkit-transform-3d),(modernizr){ ... }'
2645                        ret = testMediaQuery(['@media (',prefixes.join('transform-3d),('),mod,')','{#metromodernizr{left:9px;position:absolute;height:3px;}}'].join(''), function(div){
2646                            return div.offsetHeight === 3 && div.offsetLeft === 9;
2647                        });
2648                    }
2649                    return ret;
2650                };
2651                var testMediaQuery = function (mq, predicate) {
2652                    var st = document.createElement('style'),
2653                        div = document.createElement('div'),
2654                        ret;
2655                    st.textContent = mq;
2656                    docHead.appendChild(st);
2657                    div.id = mod;
2658                    docElement.appendChild(div);
2659                    ret = predicate(div);
2660                    st.parentNode.removeChild(st);
2661                    div.parentNode.removeChild(div);
2662                    return !!ret;
2663                };
2664                var test_touch = function() {
2665                    return canTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch ||
2666                        (typeof(window.navigator.msMaxTouchPoints) !== "undefined" && window.navigator.msMaxTouchPoints > 0) ||
2667                        testMediaQuery(['@media (',prefixes.join('touch-enabled),('),mod,')','{#metromodernizr{top:9px;position:absolute}}'].join(''), function(div){
2668                            return div.offsetTop === 9;
2669                        });
2670                };
2671                this.canTransform = !!test_props(['transformProperty', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform']);
2672                this.canTransition = test_props_all('transitionProperty');
2673                this.canTransform3d = test_3d();
2674                this.canAnimate = test_props_all('animationName');
2675                this.canTouch = test_touch();
2676                window.MetroModernizr.canTransform = this.canTransform;
2677                window.MetroModernizr.canTransition = this.canTransition;
2678                window.MetroModernizr.canTransform3d = this.canTransform3d;
2679                window.MetroModernizr.canAnimate = this.canAnimate;
2680                window.MetroModernizr.canTouch = this.canTouch;
2681                docElement = null;
2682                docHead = null;
2683                modElem = null;
2684                m_style = null;
2685            }
2686        } else {
2687            this.canTransform = $("html").hasClass("csstransforms");
2688            this.canTransition = $("html").hasClass("csstransitions");
2689            this.canTransform3d = $("html").hasClass("csstransforms3d");
2690            this.canAnimate = $("html").hasClass("cssanimations");
2691            this.canTouch = $("html").hasClass("touch") || (typeof(window.navigator.msMaxTouchPoints) !== "undefined" && window.navigator.msMaxTouchPoints > 0);
2692        }
2693    }
2694    this.canFlip3d = this.canFlip3d && this.canAnimate && this.canTransform && this.canTransform3d;
2695};
2696
2697})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.