diff --git a/Gemfile b/Gemfile
index edd8a46..2c27471 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,6 +6,9 @@ gem 'json'
 gem 'sass'
 gem 'coffee-script'
 
+gem 'therubyracer', :require => false
+gem 'barista'
+
 gem 'awesome_print', :require => 'ap'
 gem 'delayed_job'
 gem 'lastfm', :git => 'git://github.com/magnolia-fan/ruby-lastfm.git'
diff --git a/Gemfile.lock b/Gemfile.lock
index 2941804..5fc6176 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -47,6 +47,8 @@ GEM
     activesupport (3.0.9)
     arel (2.0.10)
     awesome_print (0.4.0)
+    barista (1.2.1)
+      coffee-script (~> 2.2)
     builder (2.1.2)
     coffee-script (2.2.0)
       coffee-script-source
@@ -66,6 +68,7 @@ GEM
       crack (= 0.1.8)
     i18n (0.5.0)
     json (1.5.3)
+    libv8 (3.3.10.2)
     mail (2.2.19)
       activesupport (>= 2.3.6)
       i18n (>= 0.4.0)
@@ -98,6 +101,8 @@ GEM
     rdoc (3.6.1)
     sass (3.1.3)
     sqlite3 (1.3.3)
+    therubyracer (0.9.2)
+      libv8 (~> 3.3.10)
     thor (0.14.6)
     treetop (1.4.9)
       polyglot (>= 0.3.1)
@@ -109,6 +114,7 @@ PLATFORMS
 
 DEPENDENCIES
   awesome_print
+  barista
   coffee-script
   delayed_job
   json
@@ -118,3 +124,4 @@ DEPENDENCIES
   rails (= 3.0.9)
   sass
   sqlite3
+  therubyracer
diff --git a/app/coffeescripts/ajax.coffee b/app/coffeescripts/ajax.coffee
new file mode 100644
index 0000000..270dded
--- /dev/null
+++ b/app/coffeescripts/ajax.coffee
@@ -0,0 +1,70 @@
+class Ajax
+	
+	referer: false
+	
+	loadArtistData: (name) ->
+		search.showSpinner()
+		name = name.split(' ').join('+')
+		$.get '/artist/' +name+ '/', (data) ->
+			if data.status?
+				if data.status is 'loading'
+					search.showArtistPics data.pics
+					setTimeout () ->
+						this.loadArtistData name
+					, 3000
+				else if data.status is 'corrected'
+					ajax.loadArtistData data.page
+				else if data.status is 'suggestions'
+					search.hideSpinner()
+					search.showSuggestions data.values
+				else if data.status == 'loading_failed'
+					search.hideSpinner()
+					search.showError()
+			else
+				this.setArchor '/artist/' +name+ '/'
+				pages.renderArtist data
+				beathaven.redrawScrollbar()
+	
+	loadSearchPage: ->
+		$.get '/templates/search.html', (data) ->
+			this.setArchor '/search/'
+			pages.renderSearch data
+	
+	loadSettingsPage: ->
+		$.get '/templates/settings.html', (data) ->
+			this.setArchor '/settings/'
+			pages.renderSettings data
+	
+	load404Page: ->
+		$.get '/404.html', (data) ->
+			$('.data-container .inner').html data
+			beathaven.redrawScrollbar()
+	
+	setArchor: (anchor) ->
+		@referer = this.getAnchor()
+		window.location.hash = '#' +anchor
+	
+	getAnchor: () ->
+		window.location.hash.substring 1;
+	
+	setTitle: (title) ->
+		document.title = title+ ' @ BeatHaven'
+
+	detectPage: () ->
+		if m = this.getAnchor().match /\/artist\/(.+)\//
+			this.loadArtistData m[1]
+		else if this.getAnchor() == '' or Ajax.getAnchor().match /\/search\//
+			this.loadSearchPage();
+		else if this.getAnchor().match /\/settings\//
+			this.loadSearchPage()
+		else
+			this.load404Page()
+
+
+$ ->
+	window.ajax = new Ajax()
+	$('a.data.artist').live 'click', ->
+		ajax.loadArtistData $(this).html()
+		false
+	$(window).bind 'hashchange', ->
+		ajax.detectPage()
diff --git a/app/coffeescripts/beathaven.coffee b/app/coffeescripts/beathaven.coffee
new file mode 100644
index 0000000..a7bd775
--- /dev/null
+++ b/app/coffeescripts/beathaven.coffee
@@ -0,0 +1,76 @@
+$ ->
+	l = document.location
+	if l.host not in ['beathaven.org', 'localhost']
+		l.href = 'http://beathaven.org/'+ l.hash
+	
+	window.beathaven = new BeatHaven()
+	window.vkontakte = new Vkontakte(if l.host == 'beathaven.org' then 2335068 else 2383163)
+
+	beathaven.init()
+	vkontakte.init()
+	
+	$(window).resize ->
+		beathaven.adjustSizes()
+	window.setTimeout ->
+		beathaven.checkRedrawScrollbar()
+	, 500
+
+class BeatHaven
+	
+	last_height: false
+	
+	init: ->
+		this.drawInterface()
+		this.adjustSizes()
+		ajax.detectPage()
+	
+	adjustSizes: ->
+		$('.data-container').height $(window).height() - $('.header-container').height()
+		$('.data-container').width $(window).width() - $('.player').width()
+		$('.player-container').height $(window).height()
+		$('.playlist').height $(window).height() - $('.player').height() - $('.player-container .additional-controls').height()
+		
+		$('.data-container').scrollbar()
+		$('.playlist').scrollbar()
+	
+	checkRedrawScrollbar: ->
+		focused_id = false
+		if document.activeElement.id?
+			focused_id = document.activeElement.id;
+		outer_height = $('.data-container > div').outerHeight()
+		if outer_height > 300 and outer_height != @last_height
+			@last_height = outer_height
+			this.redrawScrollbar()
+		if focused_id
+			document.getElementById(focused_id).focus()
+			focused_id = false
+		window.setTimeout ->
+			beathaven.checkRedrawScrollbar()
+		, 500
+	
+	redrawScrollbar: ->
+		$('.data-container').html $('.data-container').find('.inner').first()
+		$('.data-container').scrollbar()
+
+
+String::htmlsafe = ->
+	replaces = [
+		["\\", "\\\\"]
+		["\"", """]
+		["<", "<"]
+		[">", ">"]
+	]
+	str = this
+	for item in replaces
+		str = str.replace item[0], item[1]
+	str
+
+String::trim = ->
+	str = this
+	while str.indexOf('  ') != -1
+		str = str.replace('  ', ' ')
+	if str.charAt(0) == ' '
+		str = str.substring 1
+	if str.charAt(str.length - 1) == ' '
+		str = str.substring(0, str.length - 1)
+	str
diff --git a/app/coffeescripts/pages.coffee b/app/coffeescripts/pages.coffee
new file mode 100644
index 0000000..a364b3a
--- /dev/null
+++ b/app/coffeescripts/pages.coffee
@@ -0,0 +1,88 @@
+class Pages
+	
+	renderArtist: (data) ->
+		artist_info = $ '
+		
+			
+				

+			
+			
' +data.artist.name+ '
+			
+				' +data.artist.desc+ '
+			
+		
+					
' +album.name+ ' (' +album.year+ ')
+					
+						
+ ')
+						
+					
+					
+				
+
+						
+							
+							
' +(i+1)+ '
+							
' +track.name+ '
+							
' +track.duration+ '
+						
lose play'
+				$('#player .loaded, #player .played').width 0
+				$('.playlist-tracks li').removeClass 'now'
+			else
+				self.setTrack next
+		false
+	
+	addTrack: (artist, album, track, length, autoplay) ->
+		if not autoplay?
+			autoplay = false
+		initial_count = $('.playlist-tracks li').length
+		$('.playlist-tracks').append '
+		
+			
+				
+				
' +artist+ ' &mdash ' +track+ '
+				
' +length+ '
+				
remove
+			
'+ item.desc +'' else '')+ '
+				'
+		$('.suggestions').show()
+		false
+
+	hideSuggestions: ->
+		 $('.suggestions ul li').remove()
+ 		$('.suggestions').hide()
+		 false
+
+	showArtistPics: (pics) ->
+		$('.artist_loading.ok, .artist_pics').show()
+		for pic in pics
+			if @pics.indexOf(pic) == -1
+				@pics.push(pic);
+				$('.artist_pics').append '
+					
+						

+					
+			
+				
+				
Add some music to playlist
+				
+				
+			
+			
+			
+				
+					

Repeat
+				
+				
+					

Shuffle
+				
+				
+					

Empty Playlist
+				
+			
+		
↑
Don't forget to log in, please. It's simple.
-			
-				
-				
Add some music to playlist
-				
-				
-			
-			
-			
-				
-					

Repeat
-				
-				
-					

Shuffle
-				
-				
-					

Empty Playlist
-				
-			
-		
↑
Don't forget to log in, please. It's simple.
\
+			
\
+				

\
+			
\
+			
' + data.artist.name + '
\
+			
\
+				' + data.artist.desc + '\
+			
\
+		
\
+					
' + album.name + ' (' + album.year + ')
\
+					
\
+						
 + ')
\
+						
\
+					
\
+					
\
+				
+
\
+						\
+							
\
+							
' + (i + 1) + '\
+							
' + track.name + '
\
+							
' + track.duration + '
\
+						
lose play');
+          $('#player .loaded, #player .played').width(0);
+          return $('.playlist-tracks li').removeClass('now');
+        } else {
+          return self.setTrack(next);
+        }
+      });
+      return false;
+    };
+    Player.prototype.addTrack = function(artist, album, track, length, autoplay) {
+      var initial_count;
+      if (!(autoplay != null)) {
+        autoplay = false;
+      }
+      initial_count = $('.playlist-tracks li').length;
+      $('.playlist-tracks').append('\
+		\
+			\
+				
\
+				
' + artist + ' &mdash ' + track + '\
+				
' + length + '\
+				
remove
\
+			
' + item.desc(+'') : '') + '\
+				');
+      }
+      $('.suggestions').show();
+      return false;
+    };
+    Search.prototype.hideSuggestions = function() {
+      $('.suggestions ul li').remove();
+      $('.suggestions').hide();
+      return false;
+    };
+    Search.prototype.showArtistPics = function(pics) {
+      var pic, _i, _len;
+      $('.artist_loading.ok, .artist_pics').show();
+      for (_i = 0, _len = pics.length; _i < _len; _i++) {
+        pic = pics[_i];
+        if (this.pics.indexOf(pic) === -1) {
+          this.pics.push(pic);
+          $('.artist_pics').append('\
+					\
+						

\
+					
\
-			
\
-				

\
-			
\
-			
'+ data.artist.name +'
\
-			
\
-				'+ data.artist.desc +'\
-			
\
-		
\
-					
'+ album.name +' ('+ album.year +')
\
-					
\
-						
 +')
\
-						
\
-					
\
-					
\
-				
+
\
-						\
-							
\
-							
'+ (i+1) +'\
-							
'+ track.name +'
\
-							
'+ track.duration +'
\
-						
lose play');
-				$('#player .loaded, #player .played').width(0);
-				$('.playlist-tracks li').removeClass('now');
-			} else {
-				Player.setTrack(next);
-			}
-		});
-	},
-	
-	addTrack: function(artist, album, track, length, autoplay) {
-		if (typeof autoplay === 'undefined') {
-			autoplay = false;
-		}
-		var initial_count = $('.playlist-tracks li').length;
-		$('.playlist-tracks').append('\
-		\
-			\
-				
\
-				
'+ artist +' — '+ track +'\
-				
'+ length +'\
-				
remove
\
-			
'+ values[i].desc +'' : '') +'\
-				\
-			');
-		}
-		$('.suggestions').show();
-	},
-	
-	hideSuggestions: function() {
-		$('.suggestions ul li').remove();
- 		$('.suggestions').hide();
-	},
-	
-	showArtistPics: function(pics) {
-		$('.artist_loading.ok, .artist_pics').show();
-		for (var i = 0; i < pics.length; i++) {
-			if (Search.pics.indexOf(pics[i]) === -1) {
-				Search.pics.push(pics[i]);
-				$('.artist_pics').append('\
-					\
-						

\
-