function Car() {
	$.extend(this, {
	 
		// Properties
		el: $('#taxi'),
		timeline: new Timeline(),
		marginLeft: 0,
			
		// Methods
		init: function() {
			var that = this;
			
			that.marginLeft = parseInt(that.el.css('margin-left'));
			
			that.listenToScrollEvent();
			
			that.timeline.addKeyFrames([
				[0, function(rel) {
					that.el.removeAttr('class');
					that.el.removeAttr('style');
					
				 	that.el.addClass('car-state-just-started'); 
				 	
				 	var start = 320;
					var end = 445;			
					var pos = start + (end - start) * parseFloat(rel);
					
					that.el.css({
						top: pos
					});
					
					return pos;
				}],
				
				[500, function(rel) {
					that.el.removeAttr('class');
					that.el.removeAttr('style');
					
					that.el.addClass('car-state-picking-up');
					
					return null;
				}],
				
				[750, function(rel) {
					that.el.removeAttr('class');
					that.el.removeAttr('style');
					
					that.el.addClass('car-state-serving'); 
					
					var start = 195;
					var end = 445;			
					var pos = start + (end - start) * parseFloat(rel);
					
					that.el.css({
						top: pos
					});
					
					return pos;
				}],
				
				[2355, function(rel) {
					that.el.removeAttr('class');
					that.el.removeAttr('style');
					
					that.el.addClass('car-state-dropping-off');
					
					var pos = null;
					return pos;
				}],
				
				[2605, function(rel) {
					that.el.removeAttr('class');
					that.el.removeAttr('style');
					
					that.el.addClass('car-state-escaping'); 
					
					var start = 195;
					var end = 1000;			
					var pos = start + (end - start) * 5 * Math.pow(rel, 2);
					
					that.el.css({
						top: pos/*,
						opacity: Math.pow(1 - rel, 2)*/
					});
					
					return pos;
				}],
				
				[4000, function(rel) {
					// we're never going home!
					
					var pos = null;
					return pos;
				}]
			]);
		},
		
		listenToScrollEvent: function() {
			var that = this; 
			
			$(window).scroll(function() {
				var state;
				
				if (!IfIOSDevice()) {
					state = that.timeline.getFrame($(window).scrollTop());
					that.updateLeft();
				}
			});			
		
			$(window).resize(function() {
				if (!IfIOSDevice()) {
					that.updateLeft();
				}
			});
		},
		
		updateLeft: function() {
			var that = this;
			
			var docW = $(document).width();
			var winW = $(window).width();
			
			if (docW > winW) {
				that.el.css({
					left: docW / 2 - $(window).scrollLeft()
				});
			} else {
				that.el.css({
					'left': '50%'
				});
			}
		}
	
	});
	
	
	this.init();	
	return this;
}

function Dude() {
	$.extend(this, {
	
		// Properties
		el: $('#dude'),
		timeline: new Timeline(),
		
		// Methods
		init: function() {
			var that = this;
			
			that.listenToScrollEvent();
			
			that.timeline.addKeyFrames([
				[0, function(rel) {
					that.el.removeAttr('class');
					that.el.removeAttr('style');

					return null;
				}],
				
				[500, function(rel) {
					that.el.removeAttr('class');
					that.el.removeAttr('style');
					
					that.el.addClass('dude-state-picking-up');
					
					var start = -345;
					var end = -210;			
					var pos = start + (end - start) * parseFloat(rel);
					
					that.el.css({
						'margin-left': pos
					});
					
					return pos;
				}],
				
				[750, function(rel) {
					that.el.removeAttr('class');
					that.el.removeAttr('style');
					
					that.el.addClass('dude-state-serving');
					
					return null;
				}],
				
				[2355, function(rel) {
					that.el.removeAttr('class');
					that.el.removeAttr('style');
					
					that.el.addClass('dude-state-dropping-off');
					
					var start = -210;
					var end = -355;			
					var pos = start + (end - start) * parseFloat(rel);
					
					that.el.css({
						'margin-left': pos
					});
				}],
				
				[2605, function(rel) {
					that.el.removeAttr('class');
					that.el.removeAttr('style');
					
					that.el.addClass('dude-state-escaping'); 
					
					return null;
				}],
				
				[10000, function(rel) {
					// we're never going home!
					
					var pos = null;
					return pos;
				}]
			]);
		},
		
		listenToScrollEvent: function() {
			var that = this; 
			$(window).scroll(function() {
				var state = that.timeline.getFrame($(window).scrollTop());
			});
		}
	
	});
	
	if (!IfIOSDevice()) {
		this.init();
	}
	
	return this;

}

function Clouds() {
	$.extend(this, {
	
		// Properties
		el: $('.cloud'),
		timeline: new Timeline(),
		
		// Methods
		init: function() {
			var that = this;
			
			that.listenToScrollEvent();
			
			that.timeline.addKeyFrames([
				[0, function(rel) {
					that.el.css({
						'top': -1 * Math.floor(3000 * parseFloat(rel))
					});
					return null;
				}],
				
				[4000, function(rel) {
					// we're never going home!
					
					var pos = null;
					return pos;
				}]
			]);
		},
		
		listenToScrollEvent: function() {
			var that = this; 
			
			$(window).scroll(function() {
				var state = that.timeline.getFrame($(window).scrollTop());
			});
		}
	
	});

	if (!IfIOSDevice()) this.init();
	return this;
}

function Scene() {
	$.extend(this, {
		
		// Properties
		el: $('body'),
		elH: 0,
		viewportH: 0,
		minHeight: 0,
		
		// Methods
		init: function() {
			var that = this;
			that.elH = that.el.height();
			that.minHeight = parseInt($('body').css('min-height'));
			
			
			$(window).resize(function(){
				that.viewportH = $(window).height();
				var newH = that.elH + that.viewportH - 650; // 650 means nothing special. just don't touch it
				
				that.el.css({
					height: newH > that.minHeight ? newH : that.minHeight
				});
			});
			
			$(window).resize();
		}
	});

	this.init();
	return this;
}

function TopMenu() {
	$.extend(this, {
	
		// Properties
		el: $('.top .menu'),
		linkTop: $('#link-top'),
		linkKeyFeatures: $('#link-features'),
		linkCoverageMap: $('#link-map'),
		linkDownload: $('#link-download'),		
		timeline: new Timeline(),
		
		// Methods
		init: function() {
			var that = this;
			
			that.listenToScrollEvent();
			
			that.timeline.addKeyFrames([
				[0, function(rel) {
					that.setActive(that.linkTop);
					
					return null;
				}],
				
				[468, function(rel) {
					that.setActive(that.linkKeyFeatures);
					
					return null;
				}],
				
				[1620, function(rel) {
					that.setActive(that.linkCoverageMap);
					
					return null;
				}],
				
				[2700, function(rel) {
					that.setActive(that.linkDownload);
					
					return null;
				}],
				
				[4000, function(rel) {
					return null;
				}]
			]);
			
			$(window).scroll();
		},
		
		setActive: function(link) {
			this.el.find('.active').removeClass('active');
			link.addClass('active');
		},
		
		listenToScrollEvent: function() {
			var that = this; 
			
			$(window).scroll(function() {
				var state = that.timeline.getFrame($(window).scrollTop());
			});
		}
	
	});

	this.init();
	return this;
}

function ScrollToAnimation(conf_) {
	var defConf = {
		selector: '',
		parent: '',
		scrollTo: 0,
		duration: 300,
		factor: 1,
		easing: 'easeInOutQuad'
	};
	
	$.extend(this, {
	
		// Properties
		el: null,
		scrollTo: null,
		conf: defConf, 
		
		// Methods
		init: function() {
			var that = this;
			
			$.extend(that.conf, conf_);
			
			that.el = $(that.conf.selector);
			that.scrollTo = that.conf.scrollTo;			
			
			that.el.click(function(){
				that.handler();				
				return false;
			});
		},
		
		handler: function() {
			var that = this;
			
			var y0 = $(window).scrollTop();
			var y1 = that.scrollTo;
			
			// simple crossbrowser scroll looks like $('html, body').animate(…)
			// i don't like it
			$('body').css({
				'there-is-no-such-property': y0
			}).animate({
				'there-is-no-such-property': y1
			}, {
				duration: that.getDuration(),
				easing: that.conf.easing,
				step: function(now, fx) {
					// this is needed for iOS fixes
					$('body').addClass('animated');
					
					// crossbrowser scroll
					var y = y0 + (y1 - y0) * fx.pos;
					$(window).scrollTop(Math.round(y));
				},
				complete: function() {
					$('body').removeClass('animated');
					$(window).scroll();
				}
			});
		},
		
		getDuration: function() {
			var duration = this.conf.duration;
			duration += Math.abs($(window).scrollTop() - this.scrollTo) / this.conf.factor;
			
			return duration;
		}
	});
	
	this.init();
	return this;
}


/*
	There is no position:fixed in iOS 4 so this does the animation of car
*/
function IOSTaxiFix(conf) {
	$.extend(this, {
		// properties
		conf: {
			selector: '#taxi'
		},
		el: null,
		startPos: null,
		multitouch: false,
		
		// methods
		init: function() {
			$.extend(this.conf, conf);
			
			var that = this;
			
			that.el = $(that.conf.selector)
			that.startPos = that.el.offset().top;
			
			that.iOSFix();
		},
		
		// there is lots of problems with position:fixed in iOS
		iOSFix: function() {
			var that = this;
			
			$(window).scroll(function() {
				var scrollTop = $(window).scrollTop();
				
				that.el.stop().animate({
					'margin-top': scrollTop
				}, {
					duration: 600,
					easing: 'easeInOutCubic'
				});
			});
		}
	});
	
	this.init();
	
	return this;
}


/*
	iOS top bar fix
*/
function IOSBarFix(conf) {
	$.extend(this, {
		// properties
		conf: {
			selector: '.top'
		},
		el: null,
		elHeight: null,
		
		// methods
		init: function() {
			$.extend(this.conf, conf);
			
			var that = this;
			
			that.el = $(that.conf.selector);
			that.elHeight = that.el.outerHeight();
			
			$(window).bind('scroll', function() {
				if (!$('body').hasClass('animated')) {
					that.show.call(that);
				}
			});
			
			$(document).bind('touchmove', function() {
				that.hide();
			});
			
			that.el.find('.menu a').click(function() {
				that.hide();
			});
			
			$('#map').bind('touchmove', function(e) {
				e.stopPropagation();
			});
		},
		
		show: function() {
			var that = this;
			var scrollTop = $(window).scrollTop();
			
			that.el.css({
				'top': scrollTop
			});
		},
		
		hide: function() {
			this.el.css({
				position: 'absolute',
				top: 0
			});
		}
	});
	
	this.init();
	
	return this;
}

