SF Dok - 360° Langstrasse Zürich

$(document).ready(function() {
  var $doc = $(document);
  var $win = $(window);
  var isTouch = 'ontouchstart' in window;


  // dimensions
  var windowHeight, windowWidth;
  var fullHeight, scrollHeight;
  var bgImgWidth = 1024,
    bgImgHeight = 640;
  calculateDimensions();

  // states
  var isNight = false;
  var currentPosition = getScrollTop() / scrollHeight;
  var targetPosition = currentPosition;

  // collect timeline elements
  var $videoImage = $('.street-view > img');
  var $allNavAnchors = $('#main .navigation a');

  var $timeElements = $('[data-day]');
  var timeElements = [];
  var hotspots = {};
  $timeElements.each(function() {
    var $view = $(this);
    var id = $view.attr('id');
    var elem = new TimeElement(this);
    timeElements.push(elem);
    if (id) hotspots[id] = elem;
    var offset = $view.offset();
    $view.css({
      position: 'fixed',
      top: offset.top
    });
  });


  // handle anchor links
  $('a[href^="#"]').click(function(event) {
    event.preventDefault();
    var target = $(this).attr('href').substr(1);
    var hotspot = hotspots[target];
    if (hotspot) {
      var pos = hotspot.getPosition();
      setScrollTop(pos * scrollHeight);
    }
  });

  function setScrollTop(value) {
    $win.scrollTop(value);
  }

  function getScrollTop() {
    return $win.scrollTop() || (document.documentElement && document.documentElement.scrollTop);
  }

  function calculateDimensions() {
    windowWidth = $win.width();
    windowHeight = $win.height();
    fullHeight = $('#main').height();
    scrollHeight = fullHeight - windowHeight;
  }

  function setTargetPosition(position, immediate) {
    targetPosition = position;
    if (immediate) currentPosition = targetPosition;
  }

  function handleResize() {
    calculateDimensions();
    renderBackgroundImage();
    renderTimeline(currentPosition);
    renderNavigation();
    handleScroll();
  }

  function handleScroll() {
    setTargetPosition(getScrollTop() / scrollHeight);
    activateElement($('.navigation'), targetPosition < 0.001);
  }




  // rendering

  var scrollActivateTimeout;

  function renderTimeline(position) {
    var minY = -500,
      maxY = windowHeight + 500;
    // element position
    $.each(timeElements, function(index, element) {
      var elemPosition = element.getPosition();
      var elemY = windowHeight / 2 + element.speed * (elemPosition - position) * scrollHeight;
      var active = false;
      if (elemY < minY || elemY > maxY) {
        element.view.css({
          'visiblity': 'none',
          top: '-1000px',
          'webkitTransform': 'none'
        });
      } else {
        element.view.css('visiblity', 'visible');
        positionElement(element.view, null, elemY);
        if (elemY < windowHeight / 2) {
          var x = (windowHeight / 2 - elemY) / 100;
          x = x * x * 20;
          if (element.view.hasClass('hotspot')) {
            if (element.view.hasClass('right')) {
              element.view.css('margin-right', -x);
            } else if (element.view.hasClass('left')) {
              element.view.css('margin-left', -x);
            }
          }
        } else if (element.view.hasClass('hotspot')) {
          element.view.css({
            'margin-left': 0,
            'margin-right': 0
          });
        }

        if (element.view.is('.twitter')) {
          var sinMargin = Math.sin(elemY / 300) * 100;
          element.view.css({
            'margin-left': sinMargin,
            'margin-right': sinMargin
          });
        }
        active = Math.abs(windowHeight / 2 - elemY) < Math.max(windowHeight / 5, 100);
      }
      if (getElementActive(element.view) != active) {
        clearTimeout(element.scrollActivateTimeout);
        setElementActive(element.view, active);

        function doit() {
          activateElement(element.view, active);
        }
        if (active) {
          if (active) playSound(element.view.attr('id'));
          element.scrollActivateTimeout = setTimeout(doit, 1000);
        } else {
          doit();
        }
        activateElement(element.anchor, active);
      }
    });

    // video
    showImage(currentPosition);
  }

  function renderNavigation() {
    var MINGAP = 30;
    var $navigation = $('#main > .navigation');
    var scrollThumbHeight = Math.max(30, (windowHeight / fullHeight) * windowHeight);
    var availableHeight = windowHeight - scrollThumbHeight;
    var all = [];
    // put all hotspots in array and sort by position
    $.each(hotspots, function(k, v) {
      all.push(v);
    });
    all.sort(function(a, b) {
      return a.getPosition() - b.getPosition();
    });
    var y = 0;
    $.each(all, function(index, element) {
      if (!element.anchor) return;
      var $anchor = $(element.anchor);
      var pos = element.getPosition();
      y = Math.max(y + MINGAP, pos * availableHeight + scrollThumbHeight / 2);
      $anchor.css('top', y);
    });
  }

  function positionElement(elem, x, y) {
    if ($.browser.safari && !isTouch) {
      var xpos = (x === null ? $.data(elem, 'x') : x) || 0;
      var ypos = (y === null ? $.data(elem, 'y') : y) || 0;
      $.data(elem, 'x', xpos);
      $.data(elem, 'y', ypos);
      elem.css({
        top: -1000,
        webkitTransform: 'translate3d(' + (xpos) + 'px,' + (ypos + 1000) + 'px,0px)'
      });
    } else {
      if (x !== null) {
        elem.css('left', x);
      }
      if (y !== null) {
        elem.css('top', y);
      }
    }
  }

  function activateElement(elem, active) {
    $.data(elem, 'active', active);
    active ? elem.addClass('active') : elem.removeClass('active');
  }

  function setElementActive(elem, active) {
    $.data(elem, 'active', active);
  }

  function getElementActive(elem) {
    return $.data(elem, 'active');
  }

  function renderBackgroundImage() {
    // get image container size
    var scale = Math.max(windowHeight / bgImgHeight, windowWidth / bgImgWidth);
    var width = scale * bgImgWidth,
      height = scale * bgImgHeight;
    var left = (windowWidth - width) / 2,
      top = (windowHeight - height) / 2;
    $videoImage.width(width).height(height).css('position', 'fixed').css('left', left + 'px').css('top', top + 'px');
  }


  // main render loop
  window.requestAnimFrame = (function() {
    return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
    function( /* function */ callback, /* DOMElement */ element) {
      window.setTimeout(callback, 1000 / 60);
    };
  })();


  function animloop() {
    if (Math.floor(currentPosition * 5000) != Math.floor(targetPosition * 5000)) {
      var deaccelerate = Math.max(Math.min(Math.abs(targetPosition - currentPosition) * 5000, 10), 2);
      currentPosition += (targetPosition - currentPosition) / deaccelerate;
      renderTimeline(currentPosition);
    }
    requestAnimFrame(animloop);
  }
  animloop();




  // video handling

  var daySeqLoader = new ProgressiveImageSequence("../media/street/day/xs/vid-{index}.jpg", 1050, {
    indexSize: 4,
    onProgress: handleLoadProgress,
    onComplete: handleLoadComplete,
    stopAt: isSlowBrowser() ? 8 : 1
  });
  var imageSeqLoader = daySeqLoader;

  var loadCounterForIE = 0; // there seems to be a problem with ie calling the callback several times
  imageSeqLoader.loadPosition(currentPosition, function() {
    loadCounterForIE++;
    if (loadCounterForIE == 1) {
      showImage(currentPosition);
      imageSeqLoader.load();
      imageSeqLoader.load();
      imageSeqLoader.load();
      imageSeqLoader.load();
    }
  });

  var nightSeqLoader = new ProgressiveImageSequence("../media/street/night/xs/vid-{index}.jpg", 630, {
    indexSize: 3,
    onProgress: handleLoadProgress,
    onComplete: handleLoadComplete,
    stopAt: isSlowBrowser() ? 4 : 1
  });



  var currentSrc, currentIndex;
  var highresTimeout;

  function showImage(position) {
    var index = Math.round(currentPosition * (imageSeqLoader.length - 1));
    var img = imageSeqLoader.getNearest(index);
    var nearestIndex = imageSeqLoader.nearestIndex;
    if (nearestIndex < 0) nearestIndex = 0;
    var $img = $(img);
    var src;
    if ( !! img) {
      src = img.src;
      if (src != currentSrc) {
        $videoImage[0].src = src;
        currentSrc = src;
      }
    }
    clearTimeout(highresTimeout);
    highresTimeout = setTimeout(function() {
      if ( !! src) {
        $('#debug').text(nearestIndex + ' / ' + index + ' / ' + (Math.round(index / imageSeqLoader.length * 10000) / 100));
        var highSrc = src.split('/xs/').join('/l/');
        loadHighres(highSrc);
      }
    }, isSlowBrowser() ? 500 : 150);
  }

  window.switchStreetMode = function() {
    switchToMode(isNight ? 'day' : 'night');
  };

  window.switchToMode = function(mode) {
    $videoImage.fadeOut(function() {
      var prevHotspot, nextHotspot;
      var all = [];
      // put all hotspots in array and sort by position
      $.each(hotspots, function(k, v) {
        all.push(v);
      });
      all.sort(function(a, b) {
        return a.getPosition() - b.getPosition();
      });
      $.each(all, function(k, v) {
        if (v.getPosition() > currentPosition) {
          nextHotspot = v;
          prevHotspot = all[k - 1];
          return false;
        }
      });
      if (nextHotspot == null) {
        prevHotspot = all[all.length - 1];
      }
      var prevPos = prevHotspot ? prevHotspot.getPosition() : 0;
      var nextPos = nextHotspot ? nextHotspot.getPosition() : 1;
      var intermediatePosition = (currentPosition - prevPos) / (nextPos - prevPos);

      // let's make the switch
      isNight = mode == 'night';
      $('.toggle[rel="mode"]')[isNight ? 'addClass' : 'removeClass']('active');

      prevPos = prevHotspot ? prevHotspot.getPosition() : 0;
      nextPos = nextHotspot ? nextHotspot.getPosition() : 1;
      targetPosition = currentPosition = intermediatePosition * (nextPos - prevPos) + prevPos;

      imageSeqLoader.stop();
      imageSeqLoader.reset();
      imageSeqLoader = isNight ? nightSeqLoader : daySeqLoader;
      var loadCounterForIE = 0; // there seems to be a problem with ie calling the callback several times
      imageSeqLoader.loadPosition(currentPosition, function() {
        loadCounterForIE++;
        if (loadCounterForIE == 1) {
          setTimeout(function() {
            imageSeqLoader.load();
            imageSeqLoader.load();
            imageSeqLoader.load();
            imageSeqLoader.load();
          }, 10);
          $(window).scrollTop(targetPosition * scrollHeight);
          renderNavigation();
          renderTimeline(currentPosition);
          showImage(currentPosition);
          $videoImage.fadeIn();
        }
      });
    });
  };


  var loadHighresCounter = 0;

  function loadHighres(src) {
    var videoImage = $videoImage[0];
    videoImage.src = src;
  }


  $('body').append('<div id="loading-bar" style="position:fixed; bottom:0; left:0; background-color: #DF0012; background-color: rgba(223,0,18,0.5); height: 1px;"></div>');

  function handleLoadProgress() {
    var progress = imageSeqLoader.getLoadProgress() * 100;
    $('#loading-bar').css({
      width: progress + '%',
      opacity: 1
    });
  }

  function handleLoadComplete() {
    $('#loading-bar').css({
      width: '100%',
      opacity: 0
    });
  }




  $win.resize(handleResize);
  $win.scroll(handleScroll);

  handleResize();




  // helper classes


  $('.sound .toggle').click(function(event) {
    event.preventDefault();
    var $toggle = $(this);
    var isActive = $toggle.hasClass('active');
    if (isActive) {
      $toggle.removeClass('active');
    } else {
      $toggle.addClass('active');
    }

  });

  $('.night a').click(function(event) {
    event.preventDefault();
    switchStreetMode();
  });

  function TimeElement(view, options) {
    options = options || {};
    var $view = $(view);
    this.id = $view.attr('id');
    this.view = $view;
    this.anchor = $allNavAnchors.filter('[href="#' + $view.attr('id') + '"]');
    this.anchor = this.anchor.closest('li');
    this.getPosition = function() {
      return isNight ? this.nightPosition : this.dayPosition;
    };
    this.dayPosition = options.dayPosition || Number($view.attr('data-day') / 100);
    this.nightPosition = options.nightPosition || Number($view.attr('data-night') / 100);
    this.speed = options.speed || Number($view.attr('data-speed')) || 1;
    this.align = options.align || $view.attr('data-align') || 'left';
  }


  function easeCos(t) {
    return Math.cos((x * Math.PI + Math.PI) + 1) / 2;
  }


  function isSlowBrowser() {
    return isTouch || ($.browser.msie && Number($.browser.version) <= 8) ? true : false;
  }









  // twitter magic

  var $twitter = $('.twitter');
  $twitter.hide();
  twittersearch('langstrasse', $twitter.length, function(result) {
    $.each(result, function(index, value) {
      var $tweet = $($twitter[index]);
      $tweet.fadeIn();
      $tweet.find('p').html(value.message + ' – ' + value.user);
      $tweet.find('cite').text(value.since);
    });
  });





  // sound control

  var soundMuted = false;
  (function() {
    var ambi = document.getElementById('ambi-audio');
    if (ambi && ambi.play) {
      $('.mute').show();
      $('.mute > .toggle').click(function() {
        soundMuted = ambi.muted = !ambi.muted;
        $(this)[ambi.muted ? 'addClass' : 'removeClass']('active');
        $(this).parent()[ambi.muted ? 'addClass' : 'removeClass']('active');
      });
    }
  })();

  function playSound(name) {
    var snd = document.getElementById(name + '-audio');
    if (!soundMuted && snd && snd.play && Math.random() < 0.3) {
      if (snd.currentTime == 0 || snd.ended) {
        snd.volume = 0.4;
        snd.currentTime = 0;
        snd.play();
      }
    }
  }

  function addSounds(sounds) {
    var $body = $('body');
    $.each(sounds, function(i, name) {
      var $audio = $('<audio id="' + name + '-audio" preload="auto" autobuffer><source src="../media/sound/' + name + '.mp3"><source src="../media/sound/' + name + '.ogg"></audio>').hide();
      $body.append($audio);
    });
  }

  setTimeout(function() {
    addSounds(['host', 'zkft', 'kita', 'poli', 'hohl', 'zapo']);
  }, 1000);





  // touch override

  if (isTouch) {

    (function() {

      $('#main').css('height', 1);

      var scrollPos = 0;
      var MAXSCROLL = 10000;

      var oldCalculateDimensions = calculateDimensions;
      calculateDimensions = function() {
        oldCalculateDimensions();
        scrollHeight = MAXSCROLL;
      };

      var oldGetScrollTop = getScrollTop;
      getScrollTop = function() {
        return scrollPos;
      };

      var oldSetScrollTop = setScrollTop;
      setScrollTop = function(value) {
        scrollPos = value;
        dispatchScroll();
      };

      function dispatchScroll() {
        targetPosition = scrollPos / MAXSCROLL;
      }

      var d = document;
      var touchMoved, touchDown, touchBeginPosition;

      function onTouchStart(event) {
        event.preventDefault();
        touchDown = true;
        var touch = event.touches[0];
        touchX = touch.clientX;
        touchY = touch.clientY;
        touchBeginPosition = {
          x: touchX,
          y: touchY,
          scroll: scrollPos
        };
        d.addEventListener('touchmove', onTouchMove, false);
        d.addEventListener('touchend', onTouchEnd, false);
      }

      function onTouchMove(event) {
        event.preventDefault();
        touchMoved = true;
        var touch = event.touches[0];
        touchX = touch.clientX;
        touchY = touch.clientY;
        scrollPos = touchBeginPosition.scroll - (touchY - touchBeginPosition.y) * 2;
        scrollPos = Math.min(MAXSCROLL, Math.max(0, scrollPos));
        dispatchScroll();
      }

      function onTouchEnd(event) {
        if (touchMoved) {
          event.preventDefault();
        }

        d.removeEventListener('touchmove', onTouchMove, false);
        d.removeEventListener('touchend', onTouchEnd, false);
        touchDown = false;
      }

      d.getElementsByClassName('street-view')[0].addEventListener('touchstart', onTouchStart, false);
    })();

  }

});
$(document).ready(function(){
	var $doc = $(document);
	var $win = $(window);
	var isTouch = 'ontouchstart' in window;


	// dimensions
	var windowHeight, windowWidth;
	var fullHeight, scrollHeight;
	var bgImgWidth = 1024, bgImgHeight = 640;
	calculateDimensions();

	// states
	var isNight = false;
	var currentPosition = getScrollTop() / scrollHeight;
	var targetPosition = currentPosition;

	// collect timeline elements
	var $videoImage = $('.street-view > img');
	var $allNavAnchors = $('#main .navigation a');

	var $timeElements = $('[data-day]');
	var timeElements = [];
	var hotspots = {};
	$timeElements.each(function(){
		var $view = $(this);
		var id = $view.attr('id');
		var elem = new TimeElement(this);
		timeElements.push( elem );
		if ( id ) hotspots[id] = elem;
		var offset = $view.offset();
		$view.css({position: 'fixed' , top: offset.top });
	});


	// handle anchor links
	$('a[href^="#"]').click(function(event){
		event.preventDefault();
		var target = $(this).attr('href').substr(1);
		var hotspot = hotspots[target];
		if ( hotspot ) {
			var pos = hotspot.getPosition();
			setScrollTop( pos * scrollHeight );
		}
	});

	function setScrollTop(value) {
		$win.scrollTop(value);
	}
	
	function getScrollTop() {
		return $win.scrollTop() || (document.documentElement && document.documentElement.scrollTop);
	}
	
	function calculateDimensions() {
		windowWidth = $win.width();
		windowHeight = $win.height();
		fullHeight = $('#main').height();
		scrollHeight = fullHeight - windowHeight;
	}
	function setTargetPosition( position , immediate ) {
		targetPosition = position;
		if ( immediate ) currentPosition = targetPosition;
	}
	function handleResize() {
		calculateDimensions();
		renderBackgroundImage();
		renderTimeline( currentPosition );
		renderNavigation();
		handleScroll();
	}
	function handleScroll() {
		setTargetPosition( getScrollTop() / scrollHeight );
		activateElement( $('.navigation') , targetPosition < 0.001 );
	}




	// rendering

	var scrollActivateTimeout;

	function renderTimeline( position ) {
		var minY = -500, maxY = windowHeight + 500;
		// element position
		$.each(timeElements,function(index,element){
			var elemPosition = element.getPosition();
			var elemY = windowHeight/2 + element.speed * (elemPosition-position) * scrollHeight;
			var active = false;
			if ( elemY < minY || elemY > maxY ) {
				element.view.css({'visiblity':'none', top: '-1000px','webkitTransform':'none'});
			} else {
				element.view.css('visiblity','visible');
				positionElement(element.view,null,elemY);
				if ( elemY < windowHeight/2 ) {
					var x = (windowHeight/2 - elemY)/100;
					x = x*x * 20;
					if ( element.view.hasClass('hotspot') ) {
						if ( element.view.hasClass('right') ) {
							element.view.css('margin-right',-x);
						} else
						if ( element.view.hasClass('left') ) {
							element.view.css('margin-left',-x);
						}
					}
				} else
				if ( element.view.hasClass('hotspot') ) {
					element.view.css({'margin-left':0,'margin-right':0});
				}

				if ( element.view.is('.twitter') ) {
					var sinMargin = Math.sin(elemY/300)*100;
					element.view.css({'margin-left':sinMargin,'margin-right':sinMargin});
				}
				active = Math.abs(windowHeight/2 - elemY) < Math.max(windowHeight/5,100);
			}
			if ( getElementActive(element.view) != active ) {
				clearTimeout( element.scrollActivateTimeout );
				setElementActive(element.view,active);
				function doit() {
					activateElement( element.view , active );
				}
				if ( active ) {
					if (active) playSound(element.view.attr('id'));
					element.scrollActivateTimeout = setTimeout( doit , 1000 );
				} else {
					doit();
				}
				activateElement( element.anchor , active );
			}
		});

		// video
		showImage( currentPosition );
	}

	function renderNavigation() {
		var MINGAP = 30;
		var $navigation = $('#main > .navigation');
		var scrollThumbHeight = Math.max( 30 , (windowHeight/fullHeight)*windowHeight );
		var availableHeight = windowHeight - scrollThumbHeight;
		var all = [];
		// put all hotspots in array and sort by position
		$.each(hotspots,function(k,v){ all.push(v); });
		all.sort(function(a,b){return a.getPosition()-b.getPosition();});
		var y = 0;
		$.each(all,function(index,element){
			if ( !element.anchor ) return;
			var $anchor = $(element.anchor);
			var pos = element.getPosition();
			y = Math.max( y + MINGAP , pos * availableHeight + scrollThumbHeight/2 );
			$anchor.css('top',y);
		});
	}

	function positionElement( elem , x , y ) {
		if ( $.browser.safari && !isTouch ) {
			var xpos = ( x === null ? $.data(elem,'x') : x ) || 0;
			var ypos = ( y === null ? $.data(elem,'y') : y ) || 0;
			$.data(elem,'x',xpos);
			$.data(elem,'y',ypos);
			elem.css({top:-1000,webkitTransform:'translate3d('+(xpos)+'px,'+(ypos+1000)+'px,0px)'});
		} else {
			if ( x !== null ) {
				elem.css('left',x);
			}
			if ( y !== null ) {
				elem.css('top',y);
			}
		}
	}
	function activateElement( elem , active ) {
		$.data( elem , 'active' , active );
		active ? elem.addClass('active') : elem.removeClass('active');
	}
	function setElementActive( elem , active ) {
		$.data( elem , 'active' , active );
	}
	function getElementActive( elem ) {
		return $.data( elem , 'active' );
	}

	function renderBackgroundImage(){
		// get image container size
		var scale = Math.max( windowHeight/bgImgHeight , windowWidth/bgImgWidth );
		var width = scale * bgImgWidth , height = scale * bgImgHeight;
		var left = (windowWidth-width)/2, top = (windowHeight-height)/2;
		$videoImage
				  .width(width).height(height)
				  .css('position','fixed')
				  .css('left',left+'px')
				  .css('top',top+'px');
	}


	// main render loop
	window.requestAnimFrame = (function(){
	  return  window.requestAnimationFrame       ||
	          window.webkitRequestAnimationFrame ||
	          window.mozRequestAnimationFrame    ||
	          window.oRequestAnimationFrame      ||
	          window.msRequestAnimationFrame     ||
	          function(/* function */ callback, /* DOMElement */ element){
	            window.setTimeout(callback, 1000 / 60);
	          };
	})();


	function animloop(){
		if ( Math.floor(currentPosition*5000) != Math.floor(targetPosition*5000) ) {
			var deaccelerate = Math.max( Math.min( Math.abs(targetPosition-currentPosition)*5000 , 10 ) , 2 );
			currentPosition += (targetPosition - currentPosition) / deaccelerate;
			renderTimeline(currentPosition);
		}
	  requestAnimFrame(animloop);
	}
	animloop();




	// video handling

	var daySeqLoader = new ProgressiveImageSequence( "../media/street/day/xs/vid-{index}.jpg" , 1050 , {
		indexSize: 4,
		onProgress: handleLoadProgress,
		onComplete: handleLoadComplete,
		stopAt: isSlowBrowser() ? 8 : 1
	} );
	var imageSeqLoader = daySeqLoader;

	var loadCounterForIE = 0; // there seems to be a problem with ie calling the callback several times
	imageSeqLoader.loadPosition(currentPosition,function(){
		loadCounterForIE++;
		if ( loadCounterForIE == 1 ) {
			showImage(currentPosition);
			imageSeqLoader.load();
			imageSeqLoader.load();
			imageSeqLoader.load();
			imageSeqLoader.load();
		}
	});

	var nightSeqLoader = new ProgressiveImageSequence( "../media/street/night/xs/vid-{index}.jpg" , 630 , {
		indexSize: 3,
		onProgress: handleLoadProgress,
		onComplete: handleLoadComplete,
		stopAt: isSlowBrowser() ? 4 : 1
	} );



	var currentSrc, currentIndex;
	var highresTimeout;

	function showImage(position) {
		var index = Math.round( currentPosition * (imageSeqLoader.length-1) );
		var img = imageSeqLoader.getNearest( index );
		var nearestIndex = imageSeqLoader.nearestIndex;
		if ( nearestIndex < 0 ) nearestIndex = 0;
		var $img = $(img);
		var src;
		if ( !!img ) {
			src = img.src;
			if ( src != currentSrc ) {
				$videoImage[0].src = src;
				currentSrc = src;
			}
		}
		clearTimeout(highresTimeout);
		highresTimeout = setTimeout(function(){
			if ( !!src ) {
				$('#debug').text( nearestIndex + ' / ' + index + ' / ' + (Math.round( index / imageSeqLoader.length * 10000 ) / 100) );
				var highSrc = src.split('/xs/').join('/l/');
				loadHighres(highSrc);
			}
		},isSlowBrowser()?500:150);
	}

	window.switchStreetMode = function() {
		switchToMode( isNight ? 'day' : 'night' );
	};

	window.switchToMode = function( mode ) {
		$videoImage.fadeOut(function(){
			var prevHotspot, nextHotspot;
			var all = [];
			// put all hotspots in array and sort by position
			$.each(hotspots,function(k,v){ all.push(v); });
			all.sort(function(a,b){return a.getPosition()-b.getPosition();});
			$.each(all,function(k,v){
				if ( v.getPosition() > currentPosition ) {
					nextHotspot = v;
					prevHotspot = all[k-1];
					return false;
				}
			});
			if ( nextHotspot == null ) {
				prevHotspot = all[all.length-1];
			}
			var prevPos = prevHotspot ? prevHotspot.getPosition() : 0;
			var nextPos = nextHotspot ? nextHotspot.getPosition() : 1;
			var intermediatePosition = (currentPosition-prevPos) / (nextPos-prevPos);

			// let's make the switch
			isNight = mode == 'night';
			$('.toggle[rel="mode"]')[ isNight ? 'addClass' : 'removeClass' ]('active');

			prevPos = prevHotspot ? prevHotspot.getPosition() : 0;
			nextPos = nextHotspot ? nextHotspot.getPosition() : 1;
			targetPosition = currentPosition = intermediatePosition * (nextPos-prevPos) + prevPos;

			imageSeqLoader.stop(); imageSeqLoader.reset();
			imageSeqLoader = isNight ? nightSeqLoader : daySeqLoader;
			var loadCounterForIE = 0; // there seems to be a problem with ie calling the callback several times
			imageSeqLoader.loadPosition( currentPosition , function() {
				loadCounterForIE++;
				if ( loadCounterForIE == 1 ) {
					setTimeout(function(){
						imageSeqLoader.load();
						imageSeqLoader.load();
						imageSeqLoader.load();
						imageSeqLoader.load();
					},10);
					$(window).scrollTop(targetPosition*scrollHeight);
					renderNavigation();
					renderTimeline( currentPosition );
					showImage( currentPosition );
					$videoImage.fadeIn();
				}
			});
		});
	};


	var loadHighresCounter = 0;

	function loadHighres(src) {
		var videoImage = $videoImage[0];
		videoImage.src = src;
	}


	$('body').append('<div id="loading-bar" style="position:fixed; bottom:0; left:0; background-color: #DF0012; background-color: rgba(223,0,18,0.5); height: 1px;"></div>');
	function handleLoadProgress() {
		var progress = imageSeqLoader.getLoadProgress() * 100;
		$('#loading-bar').css({width:progress+'%',opacity:1});
	}

	function handleLoadComplete() {
		$('#loading-bar').css({width:'100%',opacity:0});
	}




	$win.resize( handleResize );
	$win.scroll( handleScroll );

	handleResize();




	// helper classes


	$('.sound .toggle').click(function(event){
		event.preventDefault();
		var $toggle = $(this);
		var isActive = $toggle.hasClass('active');
		if ( isActive ) {
			$toggle.removeClass('active');
		} else {
			$toggle.addClass('active');
		}

	});

	$('.night a').click(function(event){
		event.preventDefault();
		switchStreetMode();
	});

	function TimeElement( view , options ) {
		options = options || {};
		var $view = $(view);
		this.id = $view.attr('id');
		this.view = $view;
		this.anchor = $allNavAnchors.filter('[href="#'+$view.attr('id')+'"]');
		this.anchor = this.anchor.closest('li');
		this.getPosition = function() { return isNight? this.nightPosition : this.dayPosition; };
		this.dayPosition = options.dayPosition || Number( $view.attr('data-day')/100 );
		this.nightPosition = options.nightPosition || Number( $view.attr('data-night')/100 );
		this.speed = options.speed || Number( $view.attr('data-speed') ) || 1;
		this.align = options.align || $view.attr('data-align') || 'left';
	}


	function easeCos(t) {
		return Math.cos( (x*Math.PI+Math.PI)+1 ) / 2;
	}


	function isSlowBrowser() {
		return isTouch || ($.browser.msie && Number($.browser.version) <= 8) ? true : false;
	}












	// twitter magic

	var $twitter = $('.twitter');
	$twitter.hide();
	twittersearch('langstrasse',$twitter.length,function(result){
		$.each(result,function(index,value){
			var $tweet = $( $twitter[index] );
			$tweet.fadeIn();
			$tweet.find('p').html( value.message + ' – ' + value.user );
			$tweet.find('cite').text( value.since );
		});
	});





	// sound control

	var soundMuted = false;
	(function(){
		var ambi = document.getElementById('ambi-audio');
		if ( ambi && ambi.play ) {
			$('.mute').show();
			$('.mute > .toggle').click(function(){
				soundMuted = ambi.muted = !ambi.muted;
				$(this)[ ambi.muted ? 'addClass' : 'removeClass' ]('active');
				$(this).parent()[ ambi.muted ? 'addClass' : 'removeClass' ]('active');
			});
		}
	})();

	function playSound( name ) {
		var snd = document.getElementById(name+'-audio');
		if ( !soundMuted && snd && snd.play && Math.random() < 0.3 ) {
			if ( snd.currentTime == 0 || snd.ended ) {
				snd.volume = 0.4;
				snd.currentTime = 0;
				snd.play();
			}
		}
	}
	
	function addSounds( sounds ) {
		var $body = $('body');
		$.each( sounds , function(i,name) {
			var $audio = $('<audio id="'+name+'-audio" preload="auto" autobuffer><source src="../media/sound/'+name+'.mp3"><source src="../media/sound/'+name+'.ogg"></audio>').hide();
			$body.append( $audio );
		});
	}
	
	setTimeout(function(){
		addSounds(['host','zkft','kita','poli','hohl','zapo']);
	},1000);





	// touch override

	if ( isTouch ) {

		(function(){

			$('#main').css('height',1);

			var scrollPos = 0;
			var MAXSCROLL = 10000;

			var oldCalculateDimensions = calculateDimensions;
			calculateDimensions = function() {
				oldCalculateDimensions();
				scrollHeight = MAXSCROLL;
			};

			var oldGetScrollTop = getScrollTop;
			getScrollTop = function() {
				return scrollPos;
			};

			var oldSetScrollTop = setScrollTop;
			setScrollTop = function(value) {
				scrollPos = value;
				dispatchScroll();
			};

			function dispatchScroll() {
				targetPosition = scrollPos / MAXSCROLL;
			}

			var d = document;
			var touchMoved, touchDown, touchBeginPosition;

			function onTouchStart(event) {
				event.preventDefault();
				touchDown = true;
				var touch = event.touches[0];
				touchX = touch.clientX;
				touchY = touch.clientY;
				touchBeginPosition = { x: touchX , y: touchY , scroll: scrollPos };
				d.addEventListener('touchmove', onTouchMove, false);
				d.addEventListener('touchend', onTouchEnd, false);
			}
			function onTouchMove(event) {
			    event.preventDefault();
			    touchMoved = true;
				var touch = event.touches[0];
				touchX = touch.clientX;
				touchY = touch.clientY;
				scrollPos = touchBeginPosition.scroll - (touchY-touchBeginPosition.y) * 2;
				scrollPos = Math.min( MAXSCROLL , Math.max( 0 , scrollPos ) );
				dispatchScroll();
			}
			function onTouchEnd(event) {
				if ( touchMoved ) {
					event.preventDefault();
				}

				d.removeEventListener('touchmove', onTouchMove, false);
				d.removeEventListener('touchend', onTouchEnd, false);
				touchDown = false;
			}

			d.getElementsByClassName('street-view')[0].addEventListener('touchstart', onTouchStart, false);
		})();

	}

});