diff --git a/game/engines/default/engine/Module.lua b/game/engines/default/engine/Module.lua
index ec24a6e6c50615ea74ef1fd918bf77d96fcf0dbe..157e9835ee7a1f5dfa9ba9ce17e8f2338760ff31 100644
--- a/game/engines/default/engine/Module.lua
+++ b/game/engines/default/engine/Module.lua
@@ -238,7 +238,7 @@ function _M:instanciate(mod, name, new_game, no_reboot)
 	end
 
 	-- If bad hash, switch to dev profile
-	if not hash_valid and not profile.auth then
+	if not hash_valid or not profile.auth then
 		print("[PROFILE] switching to dev profile")
 		_G.profile = engine.PlayerProfile.new("dev")
 	end
diff --git a/game/engines/default/engine/PlayerProfile.lua b/game/engines/default/engine/PlayerProfile.lua
index 26b53d3a8603f448c49c5ebb9222ea10424ec397..f40c7db9e47420ce8013c94b95328131238898fc 100644
--- a/game/engines/default/engine/PlayerProfile.lua
+++ b/game/engines/default/engine/PlayerProfile.lua
@@ -199,8 +199,21 @@ function _M:checkFirstRun()
 	return result
 end
 
+function _M:performlogin(login, pass, name, email)
+	self.login=login
+	self.pass=pass
+	print("[ONLINE PROFILE] attempting log in ", self.login)
+	self:tryAuth()
+	if (profile.auth) then
+		self.generic.online = { login=login, pass=pass,name=name or "", email=email or "" }
+		self:saveGenericProfile("online", self.generic.online)
+		self:getConfigs("generic")
+		self:syncOnline("generic")
+	end
+end
+
 -----------------------------------------------------------------------
--- Events
+-- Events from the profile thread
 -----------------------------------------------------------------------
 
 function _M:waitEvent(name, cb)
@@ -208,7 +221,10 @@ function _M:waitEvent(name, cb)
 	local stop = false
 	local first = true
 	while not stop do
-		if not first then core.game.sleep(50) end
+		if not first then
+			core.display.forceRedraw()
+			core.game.sleep(50)
+		end
 		local evt = core.profile.popEvent()
 		while evt do
 			if type(game) == "table" then evt = game:handleProfileEvent(evt)
@@ -232,7 +248,10 @@ function _M:waitFirstAuth(timeout)
 	local first = true
 	timeout = timeout or 20
 	while self.waiting_auth and timeout > 0 do
-		if not first then core.game.sleep(50) end
+		if not first then
+			core.display.forceRedraw()
+			core.game.sleep(50)
+		end
 		local evt = core.profile.popEvent()
 		while evt do
 			if type(game) == "table" then game:handleProfileEvent(evt)
@@ -256,7 +275,7 @@ function _M:eventAuth(e)
 end
 
 function _M:eventGetNews(e)
-	if e.news then
+	if e.news and self.evt_cbs.GetNews then
 		self.evt_cbs.GetNews(e.news:unserialize())
 		self.evt_cbs.GetNews = nil
 	end
@@ -298,7 +317,7 @@ function _M:handleEvent(e)
 end
 
 -----------------------------------------------------------------------
--- Online stuff
+-- Orders for the profile thread
 -----------------------------------------------------------------------
 
 function _M:getNews(callback)
@@ -389,51 +408,23 @@ end
 
 function _M:registerSaveChardump(module, uuid, title, tags, data)
 	if not self.auth or not self.hash_valid then return end
-	local dialog = Dialog:simplePopup("Uploading character data", "Character sheet is being uploaded to http://te4.org/") dialog.__showup = nil core.display.forceRedraw()
-	local data = self:rpc{action="SaveChardump", login=self.login, hash=self.auth.hash, module=module, uuid=uuid, title=title, tags=tags, data=data}
-	game:unregisterDialog(dialog)
-	if not data or not data.ok then return end
+	core.profile.pushOrder(table.serialize{o="SaveChardump",
+		module=module,
+		uuid=uuid,
+		data=data,
+		metadata=table.serialize{tags=tags, title=title},
+	})
 	print("[ONLINE PROFILE] saved character ", uuid)
 end
 
---[[
 function _M:newProfile(Login, Name, Password, Email)
 	print("[ONLINE PROFILE] profile options ", Login, Email, Name)
-	local data = self:rpc{action="NewProfile2", login=Login, email=Email, name=Name, pass=Password}
-	if not data then print("[ONLINE PROFILE] could not create") return end
-	print("[ONLINE PROFILE] profile id ", data.id)
-	self:performlogin(Login, Password, Name, Email)
-end
 
-function _M:performlogin(login, pass, name, email)
-	self.login=login
-	self.pass=pass
-	print("[ONLINE PROFILE] attempting log in ", self.login)
-	self:tryAuth()
-	if (profile.auth) then
-		self.generic.online = { login=login, pass=pass,name=name or "", email=email or "" }
-		self:saveGenericProfile("online", self.generic.online)
-		self:getConfigs("generic")
-		self:syncOnline("generic")
-	end
-end
-
-function _M:registerNewCharacter(module)
-	if not self.auth or not self.hash_valid then return end
-	local dialog = Dialog:simplePopup("Registering character", "Character is being registered on http://te4.org/") dialog.__showup = nil core.display.forceRedraw()
-	local data = self:rpc{action="RegisterNewCharacter", login=self.login, hash=self.auth.hash, module=module}
-	game:unregisterDialog(dialog)
-	if not data then return end
-	print("[ONLINE PROFILE] new character UUID ", data.uuid)
-	return data.uuid
-end
+	core.profile.pushOrder(table.serialize{o="NewProfile2", login=Login, email=Email, name=Name, pass=Password})
+	local id = nil
+	self:waitEvent("NewProfile2", function(e) id = e.id end)
 
-function _M:registerSaveChardump(module, uuid, title, tags, data)
-	if not self.auth or not self.hash_valid then return end
-	local dialog = Dialog:simplePopup("Uploading character data", "Character sheet is being uploaded to http://te4.org/") dialog.__showup = nil core.display.forceRedraw()
-	local data = self:rpc{action="SaveChardump", login=self.login, hash=self.auth.hash, module=module, uuid=uuid, title=title, tags=tags, data=data}
-	game:unregisterDialog(dialog)
-	if not data or not data.ok then return end
-	print("[ONLINE PROFILE] saved character ", uuid)
+	if not id then print("[ONLINE PROFILE] could not create") return end
+	print("[ONLINE PROFILE] profile id ", id)
+	self:performlogin(Login, Password, Name, Email)
 end
-]]
diff --git a/game/engines/default/engine/ui/Button.lua b/game/engines/default/engine/ui/Button.lua
index 8b49743c9d9f349db53b34e98a70061c0b737e8a..a105864189c7b0971f53b86b37b571cf95a28927 100644
--- a/game/engines/default/engine/ui/Button.lua
+++ b/game/engines/default/engine/ui/Button.lua
@@ -72,10 +72,15 @@ function _M:generate()
 	self.w, self.h = fw+10, fh+10
 end
 
-function _M:display(x, y)
+function _M:display(x, y, nb_keyframes)
 	if self.focused then
 		self.stex:toScreenFull(x+5, y+5, self.rw, self.rh, self.tex_w, self.tex_h)
 	else
 		self.tex:toScreenFull(x+5, y+5, self.rw, self.rh, self.tex_w, self.tex_h)
+		if self.focus_decay then
+			self.stex:toScreenFull(x+5, y+5, self.rw, self.rh, self.tex_w, self.tex_h, 1, 1, 1, self.focus_decay / self.focus_decay_max_d)
+			self.focus_decay = self.focus_decay - nb_keyframes
+			if self.focus_decay <= 0 then self.focus_decay = nil end
+		end
 	end
 end
diff --git a/game/engines/default/engine/ui/Checkbox.lua b/game/engines/default/engine/ui/Checkbox.lua
index fa828861cd700ac2eeff4f5f61f342b89f5f7a80..17095f7be3ef9a5f64408210265e7d06b5083097 100644
--- a/game/engines/default/engine/ui/Checkbox.lua
+++ b/game/engines/default/engine/ui/Checkbox.lua
@@ -92,11 +92,16 @@ function _M:select()
 	self.checked = not self.checked
 end
 
-function _M:display(x, y)
+function _M:display(x, y, nb_keyframes)
 	if self.focused then
 		self.stex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
 	else
 		self.tex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
+		if self.focus_decay then
+			self.stex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h, 1, 1, 1, self.focus_decay / self.focus_decay_max_d)
+			self.focus_decay = self.focus_decay - nb_keyframes
+			if self.focus_decay <= 0 then self.focus_decay = nil end
+		end
 	end
 	if self.checked then
 		self.chk_tex:toScreenFull(x + self.w - self.chk_w, y, self.chk_w, self.chk_h, self.chk_tex_w, self.chk_tex_h)
diff --git a/game/engines/default/engine/ui/Dialog.lua b/game/engines/default/engine/ui/Dialog.lua
index e1f8007046338493a6d64aff536bee10ea06cf61..2bc4f390ef445e70379c92c45add99eefa274d79 100644
--- a/game/engines/default/engine/ui/Dialog.lua
+++ b/game/engines/default/engine/ui/Dialog.lua
@@ -387,7 +387,7 @@ function _M:toScreen(x, y, nb_keyframes)
 		self.tex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
 		for i = 1, #self.uis do
 			local ui = self.uis[i]
-			ui.ui:display(x + ui.x, y + ui.y)
+			ui.ui:display(x + ui.x, y + ui.y, nb_keyframes)
 		end
 	end
 end
diff --git a/game/engines/default/engine/ui/Focusable.lua b/game/engines/default/engine/ui/Focusable.lua
index 03818bfe03637e74c7f850b6d033720fe64f0a79..03383cfa3eb631f7c4e848091109a766fa692d29 100644
--- a/game/engines/default/engine/ui/Focusable.lua
+++ b/game/engines/default/engine/ui/Focusable.lua
@@ -23,7 +23,12 @@ require "engine.class"
 module(..., package.seeall, class.make)
 
 can_focus = true
+focus_decay_max = 5
+focus_decay_max_d = 8
 
 function _M:setFocus(v)
 	self.focused = v
+	if not v then
+		self.focus_decay = self.focus_decay_max
+	end
 end
diff --git a/game/engines/default/engine/ui/List.lua b/game/engines/default/engine/ui/List.lua
index 23ddf26924718a1f62e95a4a9f6fd17c6d29dfbc..725f26f84c3a427edb2131dda3ed9588568843d4 100644
--- a/game/engines/default/engine/ui/List.lua
+++ b/game/engines/default/engine/ui/List.lua
@@ -119,28 +119,39 @@ function _M:generate()
 		if button == "wheelup" and event == "button" then self.scroll = util.bound(self.scroll - 1, 1, self.max - self.max_display + 1)
 		elseif button == "wheeldown" and event == "button" then self.scroll = util.bound(self.scroll + 1, 1, self.max - self.max_display + 1) end
 
+		if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 		self.sel = util.bound(self.scroll + math.floor(by / self.fh), 1, self.max)
 		if (self.all_clicks or button == "left") and event == "button" then self:onUse(button) end
 	end)
 	self.key:addBinds{
 		ACCEPT = function() self:onUse() end,
-		MOVE_UP = function() self.sel = util.boundWrap(self.sel - 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) end,
-		MOVE_DOWN = function() self.sel = util.boundWrap(self.sel + 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) end,
+		MOVE_UP = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
+			self.sel = util.boundWrap(self.sel - 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
+		end,
+		MOVE_DOWN = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
+			self.sel = util.boundWrap(self.sel + 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
+		end,
 	}
 	self.key:addCommands{
 		_HOME = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = 1
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 		end,
 		_END = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = self.max
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 		end,
 		_PAGEUP = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = util.bound(self.sel - self.max_display, 1, self.max)
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 		end,
 		_PAGEDOWN = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = util.bound(self.sel + self.max_display, 1, self.max)
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 		end,
@@ -148,6 +159,7 @@ function _M:generate()
 end
 
 function _M:select(i)
+	if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 	self.sel = util.bound(i, 1, #self.list)
 	self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 end
@@ -159,7 +171,7 @@ function _M:onUse(...)
 	else self.fct(item, self.sel, ...) end
 end
 
-function _M:display(x, y)
+function _M:display(x, y, nb_keyframes)
 	local bx, by = x, y
 
 	local max = math.min(self.scroll + self.max_display - 1, self.max)
@@ -167,13 +179,16 @@ function _M:display(x, y)
 		local item = self.list[i]
 		if not item then break end
 		if self.sel == i then
-			if self.focused then
-				item._stex:toScreenFull(x, y, self.fw, self.fh, item._tex_w, item._tex_h)
-			else
-				item._sustex:toScreenFull(x, y, self.fw, self.fh, item._tex_w, item._tex_h)
-			end
+			if self.focused then item._stex:toScreenFull(x, y, self.fw, self.fh, item._tex_w, item._tex_h)
+			else item._sustex:toScreenFull(x, y, self.fw, self.fh, item._tex_w, item._tex_h) end
 		else
 			item._tex:toScreenFull(x, y, self.fw, self.fh, item._tex_w, item._tex_h)
+			if item.focus_decay then
+				if self.focused then item._stex:toScreenFull(x, y, self.fw, self.fh, item._tex_w, item._tex_h, 1, 1, 1, item.focus_decay / self.focus_decay_max_d)
+				else item._sustex:toScreenFull(x, y, self.fw, self.fh, item._tex_w, item._tex_h, 1, 1, 1, item.focus_decay / self.focus_decay_max_d) end
+				item.focus_decay = item.focus_decay - nb_keyframes
+				if item.focus_decay <= 0 then item.focus_decay = nil end
+			end
 		end
 		y = y + self.fh
 	end
diff --git a/game/engines/default/engine/ui/ListColumns.lua b/game/engines/default/engine/ui/ListColumns.lua
index e84e6ab107a5f7a40e62e85606f787d2c755ea5f..e3d6ce659ffebe19ded9b477ee5399ce2a4daa12 100644
--- a/game/engines/default/engine/ui/ListColumns.lua
+++ b/game/engines/default/engine/ui/ListColumns.lua
@@ -188,32 +188,43 @@ function _M:generate()
 		if button == "wheelup" and event == "button" then self.scroll = util.bound(self.scroll - 1, 1, self.max - self.max_display + 1)
 		elseif button == "wheeldown" and event == "button" then self.scroll = util.bound(self.scroll + 1, 1, self.max - self.max_display + 1) end
 
+		if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 		self.sel = util.bound(self.scroll + math.floor(by / self.fh), 1, self.max)
 		self:onSelect()
 		if (self.all_clicks or button == "left") and event == "button" then self:onUse(button, event) end
 	end)
 	self.key:addBinds{
 		ACCEPT = function() self:onUse("left", "key") end,
-		MOVE_UP = function() self.sel = util.boundWrap(self.sel - 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect() end,
-		MOVE_DOWN = function() self.sel = util.boundWrap(self.sel + 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect() end,
+		MOVE_UP = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
+			self.sel = util.boundWrap(self.sel - 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect()
+		end,
+		MOVE_DOWN = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
+			self.sel = util.boundWrap(self.sel + 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect()
+		end,
 	}
 	self.key:addCommands{
 		_HOME = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = 1
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 			self:onSelect()
 		end,
 		_END = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = self.max
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 			self:onSelect()
 		end,
 		_PAGEUP = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = util.bound(self.sel - self.max_display, 1, self.max)
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 			self:onSelect()
 		end,
 		_PAGEDOWN = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = util.bound(self.sel + self.max_display, 1, self.max)
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 			self:onSelect()
@@ -268,7 +279,7 @@ function _M:selectColumn(i, force)
 	end
 end
 
-function _M:display(x, y)
+function _M:display(x, y, nb_keyframes)
 	local bx, by = x, y
 	for j = 1, #self.columns do
 		local col = self.columns[j]
@@ -287,13 +298,16 @@ function _M:display(x, y)
 			local item = self.list[i]
 			if not item then break end
 			if self.sel == i then
-				if self.focused then
-					col._istex[1]:toScreenFull(x, y, col.fw, self.fh, col._istex[2], col._istex[3])
-				else
-					col._isustex[1]:toScreenFull(x, y, col.fw, self.fh, col._isustex[2], col._isustex[3])
-				end
+				if self.focused then col._istex[1]:toScreenFull(x, y, col.fw, self.fh, col._istex[2], col._istex[3])
+				else col._isustex[1]:toScreenFull(x, y, col.fw, self.fh, col._isustex[2], col._isustex[3]) end
 			else
 				col._itex[1]:toScreenFull(x, y, col.fw, self.fh, col._itex[2], col._itex[3])
+				if item.focus_decay then
+					if self.focused then col._istex[1]:toScreenFull(x, y, col.fw, self.fh, col._istex[2], col._istex[3], 1, 1, 1, item.focus_decay / self.focus_decay_max_d)
+					else col._isustex[1]:toScreenFull(x, y, col.fw, self.fh, col._isustex[2], col._isustex[3], 1, 1, 1, item.focus_decay / self.focus_decay_max_d) end
+					item.focus_decay = item.focus_decay - nb_keyframes
+					if item.focus_decay <= 0 then item.focus_decay = nil end
+				end
 			end
 			if col.direct_draw then
 				col.direct_draw(item, x, y, col.fw, self.fh)
diff --git a/game/engines/default/engine/ui/Numberbox.lua b/game/engines/default/engine/ui/Numberbox.lua
index 4174c6384d50acfeefb9f2fe9801d5bb9d170f83..24bd47d7bc3082ab95da327bb527718da84c0844 100644
--- a/game/engines/default/engine/ui/Numberbox.lua
+++ b/game/engines/default/engine/ui/Numberbox.lua
@@ -165,11 +165,16 @@ function _M:updateText(v)
 	self.text_surf:updateTexture(self.text_tex)
 end
 
-function _M:display(x, y)
+function _M:display(x, y, nb_keyframes)
 	if self.focused then
 		self.stex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
 	else
 		self.tex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
+		if self.focus_decay then
+			self.stex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h, 1, 1, 1, self.focus_decay / self.focus_decay_max_d)
+			self.focus_decay = self.focus_decay - nb_keyframes
+			if self.focus_decay <= 0 then self.focus_decay = nil end
+		end
 	end
 	self.text_tex:toScreenFull(x + self.text_x, y + self.text_y, self.fw, self.fh, self.text_tex_w, self.text_tex_h)
 	self.cursor_tex:toScreenFull(x + self.text_x + (self.cursor-self.scroll) * self.font_mono_w, y + self.text_y, self.cursor_w, self.cursor_h, self.cursor_tex_w, self.cursor_tex_h)
diff --git a/game/engines/default/engine/ui/Textbox.lua b/game/engines/default/engine/ui/Textbox.lua
index fa1cdd8a17f6f765e66fe0c3742538463ffb503f..82433985dbae086e3928e9b07780de1f34ddd00b 100644
--- a/game/engines/default/engine/ui/Textbox.lua
+++ b/game/engines/default/engine/ui/Textbox.lua
@@ -150,12 +150,17 @@ function _M:updateText()
 	self.text_surf:updateTexture(self.text_tex)
 end
 
-function _M:display(x, y)
+function _M:display(x, y, nb_keyframes)
 	if self.focused then
 		self.stex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
 		self.cursor_tex:toScreenFull(x + self.text_x + (self.cursor-self.scroll) * self.font_mono_w, y + self.text_y, self.cursor_w, self.cursor_h, self.cursor_tex_w, self.cursor_tex_h)
 	else
 		self.tex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h)
+		if self.focus_decay then
+			self.stex:toScreenFull(x, y, self.w, self.h, self.tex_w, self.tex_h, 1, 1, 1, self.focus_decay / self.focus_decay_max_d)
+			self.focus_decay = self.focus_decay - nb_keyframes
+			if self.focus_decay <= 0 then self.focus_decay = nil end
+		end
 	end
 	self.text_tex:toScreenFull(x + self.text_x, y + self.text_y, self.fw, self.fh, self.text_tex_w, self.text_tex_h)
 end
diff --git a/game/engines/default/engine/ui/TreeList.lua b/game/engines/default/engine/ui/TreeList.lua
index e3adc271afa687552bece6ff7d65eb138e7c5636..10c04b34b9414a47349b9cc001e53714483d84cb 100644
--- a/game/engines/default/engine/ui/TreeList.lua
+++ b/game/engines/default/engine/ui/TreeList.lua
@@ -203,6 +203,7 @@ function _M:generate()
 		if button == "wheelup" and event == "button" then self.scroll = util.bound(self.scroll - 1, 1, self.max - self.max_display + 1)
 		elseif button == "wheeldown" and event == "button" then self.scroll = util.bound(self.scroll + 1, 1, self.max - self.max_display + 1) end
 
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 		self.sel = util.bound(self.scroll + math.floor(by / self.fh), 1, self.max)
 		if self.sel_by_col then
 			for i = 1, #self.sel_by_col do if bx > (self.sel_by_col[i-1] or 0) and bx <= self.sel_by_col[i] then
@@ -219,8 +220,14 @@ function _M:generate()
 	end)
 	self.key:addBinds{
 		ACCEPT = function() self:onUse("left") end,
-		MOVE_UP = function() self.sel = util.boundWrap(self.sel - 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect() end,
-		MOVE_DOWN = function() self.sel = util.boundWrap(self.sel + 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect() end,
+		MOVE_UP = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
+			self.sel = util.boundWrap(self.sel - 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect()
+		end,
+		MOVE_DOWN = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
+			self.sel = util.boundWrap(self.sel + 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect()
+		end,
 	}
 	if self.sel_by_col then
 		self.key:addBinds{
@@ -230,21 +237,25 @@ function _M:generate()
 	end
 	self.key:addCommands{
 		_HOME = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = 1
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 			self:onSelect()
 		end,
 		_END = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = self.max
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 			self:onSelect()
 		end,
 		_PAGEUP = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = util.bound(self.sel - self.max_display, 1, self.max)
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 			self:onSelect()
 		end,
 		_PAGEDOWN = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = util.bound(self.sel + self.max_display, 1, self.max)
 			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
 			self:onSelect()
@@ -303,7 +314,7 @@ function _M:onUse(...)
 	else self.fct(item, self.sel, ...) end
 end
 
-function _M:display(x, y)
+function _M:display(x, y, nb_keyframes)
 	local bx, by = x, y
 
 	local max = math.min(self.scroll + self.max_display - 1, self.max)
@@ -315,10 +326,15 @@ function _M:display(x, y)
 			if not item then break end
 			local backs = self:getColumnBackground(col, item.level, item.nodes, item.shown)
 			if self.sel == i and (not self.sel_by_col or self.cur_col == j) then
-				if self.focused then
-					backs._stex:toScreenFull(x, y, col.width, self.fh, backs._stex_w, backs._stex_h)
-				else
-					backs._sustex:toScreenFull(x, y, col.width, self.fh, backs._sustex_w, backs._sustex_h)
+				if self.focused then backs._stex:toScreenFull(x, y, col.width, self.fh, backs._stex_w, backs._stex_h)
+				else backs._sustex:toScreenFull(x, y, col.width, self.fh, backs._sustex_w, backs._sustex_h) end
+			elseif (not self.sel_by_col or self.cur_col == j) then
+				backs._tex:toScreenFull(x, y, col.width, self.fh, backs._tex_w, backs._tex_h)
+				if item.focus_decay then
+					if self.focused then backs._stex:toScreenFull(x, y, col.width, self.fh, backs._stex_w, backs._stex_h, 1, 1, 1, item.focus_decay / self.focus_decay_max_d)
+					else backs._sustex:toScreenFull(x, y, col.width, self.fh, backs._sustex_w, backs._sustex_h, 1, 1, 1, item.focus_decay / self.focus_decay_max_d) end
+					item.focus_decay = item.focus_decay - nb_keyframes
+					if item.focus_decay <= 0 then item.focus_decay = nil end
 				end
 			else
 				backs._tex:toScreenFull(x, y, col.width, self.fh, backs._tex_w, backs._tex_h)
diff --git a/game/engines/default/engine/ui/VariableList.lua b/game/engines/default/engine/ui/VariableList.lua
index 396cce88901b5ab7572f484bcf98751933f9912f..18d897544fa55be7a2fbebbdcb8c3091d9becece 100644
--- a/game/engines/default/engine/ui/VariableList.lua
+++ b/game/engines/default/engine/ui/VariableList.lua
@@ -85,6 +85,7 @@ function _M:generate()
 		item._stex = ss:glTexture()
 
 		self.mouse:registerZone(0, self.h, self.w, fh, function(button, x, y, xrel, yrel, bx, by, event)
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
 			self.sel = i
 			self:onSelect()
 			if button == "left" and event == "button" then self:onUse() end
@@ -96,8 +97,14 @@ function _M:generate()
 	-- Add UI controls
 	self.key:addBinds{
 		ACCEPT = function() self:onUse() end,
-		MOVE_UP = function() self.sel = util.boundWrap(self.sel - 1, 1, self.max) self:onSelect() end,
-		MOVE_DOWN = function() self.sel = util.boundWrap(self.sel + 1, 1, self.max) self:onSelect() end,
+		MOVE_UP = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
+			self.sel = util.boundWrap(self.sel - 1, 1, self.max) self:onSelect()
+		end,
+		MOVE_DOWN = function()
+			if self.sel then self.list[self.sel].focus_decay = self.focus_decay_max end
+			self.sel = util.boundWrap(self.sel + 1, 1, self.max) self:onSelect()
+		end,
 	}
 end
 
@@ -115,18 +122,21 @@ function _M:onSelect()
 	if rawget(self, "select") then self.select(item, self.sel) end
 end
 
-function _M:display(x, y)
+function _M:display(x, y, nb_keyframes)
 	for i = 1, self.max do
 		local item = self.list[i]
 		if not item then break end
 		if self.sel == i then
-			if self.focused then
-				item._stex:toScreenFull(x, y, self.fw, item.fh, item._tex_w, item._tex_h)
-			else
-				item._tex:toScreenFull(x, y, self.fw, item.fh, item._tex_w, item._tex_h)
-			end
+			if self.focused then item._stex:toScreenFull(x, y, self.fw, item.fh, item._tex_w, item._tex_h)
+			else item._tex:toScreenFull(x, y, self.fw, item.fh, item._tex_w, item._tex_h) end
 		else
 			item._tex:toScreenFull(x, y, self.fw, item.fh, item._tex_w, item._tex_h)
+			if item.focus_decay then
+				if self.focused then item._stex:toScreenFull(x, y, self.fw, item.fh, item._tex_w, item._tex_h, 1, 1, 1, item.focus_decay / self.focus_decay_max_d)
+				else item._tex:toScreenFull(x, y, self.fw, item.fh, item._tex_w, item._tex_h, 1, 1, 1, item.focus_decay / self.focus_decay_max_d) end
+				item.focus_decay = item.focus_decay - nb_keyframes
+				if item.focus_decay <= 0 then item.focus_decay = nil end
+			end
 		end
 		y = y + item.fh
 	end
diff --git a/game/profile-thread/Client.lua b/game/profile-thread/Client.lua
index 503539c87f43004452ae9f96fd5495329c4815a7..94eea046283083a741f896d5ff933b4571f00273 100644
--- a/game/profile-thread/Client.lua
+++ b/game/profile-thread/Client.lua
@@ -29,19 +29,40 @@ function _M:connected()
 	if self.sock then return true end
 	self.sock = socket.connect("te4.org", 2257)
 	if not self.sock then return false end
-	self.sock:settimeout(10)
+--	self.sock:settimeout(10)
 	print("[PROFILE] Thread connected to te4.org")
 	self:login()
 	return true
 end
 
+--- Connects the second tcp channel to receive data
+function _M:connectedPull()
+	if self.psock then return true end
+	self.psock = socket.connect("te4.org", 2258)
+	if not self.psock then return false end
+	print("[PROFILE] Pull socket connected to te4.org")
+--	self.psock:send("
+	return true
+end
+
 function _M:write(str, ...)
 	self.sock:send(str:format(...))
 end
 
+function _M:disconnect()
+	self.sock = nil
+	self.auth = nil
+end
+
 function _M:read(ncode)
-	local l = self.sock:receive("*l")
-	if not l then return end
+	local l, err = self.sock:receive("*l")
+	if not l then
+		if err == "closed" then
+			print("[PROFILE] connection disrupted, trying to reconnect", err)
+			self:disconnect()
+		end
+		return nil
+	end
 	if ncode and l:sub(1, 3) ~= ncode then
 		return nil, "bad code"
 	end
@@ -111,6 +132,15 @@ function _M:orderLogin(o)
 	end
 end
 
+function _M:orderLogoff(o)
+	-- Already logged?
+	if self.auth then
+		print("[PROFILE] logoff", self.auth.name)
+		cprofile.pushEvent("e='Logoff'")
+		self.auth = nil
+	end
+end
+
 function _M:orderGetNews(o)
 	self:command("NEWS")
 	if self:read("200") then
@@ -171,6 +201,15 @@ function _M:orderRegisterNewCharacter(o)
 	end
 end
 
+function _M:orderSaveChardump(o)
+	self:command("CHAR", "UPDATE", o.metadata:len(), o.data:len(), o.uuid, o.module)
+	if not self:read("200") then return end
+	self.sock:send(o.metadata)
+	if not self:read("200") then return end
+	self.sock:send(o.data)
+	cprofile.pushEvent("e='SaveChardump' ok=true")
+end
+
 function _M:handleOrder(o)
 	o = o:unserialize()
 	if not self.sock and o.o ~= "Login" then return end -- Dont do stuff without a connection, unless we try to auth
diff --git a/src/core_lua.c b/src/core_lua.c
index bab5a924decab868fc083ff35a43f508d5090800..ebbf4563457b4ac7ba7d1d988ff2ed19de5e6a0a 100644
--- a/src/core_lua.c
+++ b/src/core_lua.c
@@ -972,10 +972,10 @@ static int sdl_texture_toscreen_full(lua_State *L)
 	int rh = luaL_checknumber(L, 7);
 	if (lua_isnumber(L, 8))
 	{
-		float r = luaL_checknumber(L, 9);
-		float g = luaL_checknumber(L, 10);
-		float b = luaL_checknumber(L, 11);
-		float a = luaL_checknumber(L, 12);
+		float r = luaL_checknumber(L, 8);
+		float g = luaL_checknumber(L, 9);
+		float b = luaL_checknumber(L, 10);
+		float a = luaL_checknumber(L, 11);
 		GLfloat colors[4*4] = {
 			r, g, b, a,
 			r, g, b, a,
diff --git a/src/profile.c b/src/profile.c
index 477ad459362bc616230f3a91ffb15e7349c5ea42..39c32c70ffc8be7f4f272d32d56ae093c081bbfc 100644
--- a/src/profile.c
+++ b/src/profile.c
@@ -35,7 +35,7 @@ int push_order(lua_State *L)
 {
 	size_t len;
 	const char *code = luaL_checklstring(L, 1, &len);
-	printf("[profile order] %s\n", code);
+//	printf("[profile order] %s\n", code);
 
 	profile_queue *q = malloc(sizeof(profile_queue));
 	char *d = calloc(len, sizeof(char));
@@ -81,7 +81,7 @@ int push_event(lua_State *L)
 {
 	size_t len;
 	const char *code = luaL_checklstring(L, 1, &len);
-	printf("[profile event] %s\n", code);
+//	printf("[profile event] %s\n", code);
 
 	profile_queue *q = malloc(sizeof(profile_queue));
 	char *d = calloc(len, sizeof(char));