diff --git a/res/search.js b/res/search.js
index f9d99fd25238713f381caf4f136bae362f7883a3..f0a29d1ce3e770612a2fb51193e84194c1383525 100644
--- a/res/search.js
+++ b/res/search.js
@@ -1,376 +1,317 @@
-var state = {"query": "", "selected_history_entry": -1};
-var buffers = {};
-var open = [];
-
-var max_history_size = 8;
-
-var add_to_history = function (query) {
-    if (!query)
-        return;
-
-    var history = get_history().filter(function (x) {
-        return x != query;
-    });
-    history.push(query);
-    history = history.slice(Math.max(0, history.length - max_history_size));
-    localStorage.setItem('history', JSON.stringify(history));
-};
-
-var get_history = function () {
-    return JSON.parse(localStorage.getItem('history')) || [];
-};
-
-var wrap_click_handler = function (fun) {
-    return function (event) {
-        event.stopPropagation();
-        fun(event);
+var translation = translations.en;
+
+var state = {
+    query: "",
+    selected_history_entry: -1,
+    buffers: {},
+    open: []
+};
+
+var search_history = {
+    max_size: 8,
+    get: function () {
+        return (JSON.parse(localStorage.getItem('history')) || []).reverse();
+    },
+    clear: function () {
+        localStorage.removeItem('history');
+    },
+    push: function (query) {
+        if (!query)
+            return;
+
+        var tmp = search_history.get().filter(function (x) {
+            return x != query;
+        });
+        tmp.push(query);
+        tmp = tmp.slice(Math.max(0, tmp.length - tmp.max_size));
+        localStorage.setItem('history', JSON.stringify(tmp));
     }
 };
 
-$("#q").keypress(function (e) {
-    var key = e.which || e.keyCode;
-    if (key === 13) {
-        search();
-    }
-    if (key === 40) {
-        $("#history" + state.selected_history_entry).removeClass("selected");
-        state.selected_history_entry = (state.selected_history_entry + 1) % get_history().length;
-        $("#q").val(get_history().reverse()[state.selected_history_entry]);
-        $("#history" + state.selected_history_entry).addClass("selected");
-    } else if (key === 38) {
-        $("#history" + state.selected_history_entry).removeClass("selected");
-        if (state.selected_history_entry === 0) {
-            state.selected_history_entry = -1;
-            $("#q").val("");
-        } else {
-            state.selected_history_entry = (state.selected_history_entry - 1) % get_history().length;
-            $("#q").val(get_history().reverse()[state.selected_history_entry]);
-            $("#history" + state.selected_history_entry).addClass("selected");
-        }
-    }
-});
-
-$("#q").focus(function () {
-    $("#autocomplete").addClass("active");
-    $("#results").addClass("hidden");
-    $("nav").addClass("search");
-});
-$("#q").blur(function () {
-    $("#autocomplete").removeClass("active");
-    $("#results").removeClass("hidden");
-    $("nav").removeClass("search");
-});
-
-var sendercolor = function (nick) {
-    var sendercolors = [
-        "#e90d7f",
-        "#8e55e9",
-        "#b30e0e",
-        "#17b339",
-        "#58afb3",
-        "#9d54b3",
-        "#b39775",
-        "#3176b3",
-        "#e90d7f",
-        "#8e55e9",
-        "#b30e0e",
-        "#17b339",
-        "#58afb3",
-        "#9d54b3",
-        "#b39775",
-        "#3176b3"
-    ];
-
-    var reflect = function (crc, n) {
-        var j = 1, crcout = 0;
-        for (var i = (1 << (n - 1)); i > 0; i >>= 1) {
-            if ((crc & i) > 0) {
-                crcout |= j;
-            }
-            j <<= 1;
-        }
-        return crcout;
-    };
-
-    var qChecksum = function (str) {
-        var crc = 0xffff;
-        var crcHighBitMask = 0x8000;
-
-        for (var i = 0; i < str.length; i++) {
-            var b = str.codePointAt(i);
-            var c = reflect(b, 8);
-            for (var j = 0x80; j > 0; j >>= 1) {
-                var highBit = crc & crcHighBitMask;
-                crc <<= 1;
-                if ((c & j) > 0) {
-                    highBit ^= crcHighBitMask;
-                }
-                if (highBit > 0) {
-                    crc ^= 0x1021;
+var render = {
+    overview: function (ids) {
+        $("#results").children().remove();
+
+        for (var i = 0; i < ids.length; i++) {
+            var buffer = ids[i];
+
+            if (!state.buffers.hasOwnProperty(buffer.bufferid)) {
+                var ctx = 0;
+                state.buffers[buffer.bufferid] = {
+                    id: buffer.bufferid,
+                    name: buffer.buffername,
+                    network: buffer.networkname,
+                    selected: false,
+                    contexts: buffer.messages.map(function (message) {
+                        return {
+                            "selected": false,
+                            "original": message,
+                            "before": [],
+                            "after": [],
+                            "buffer": buffer.bufferid,
+                            "id": ctx++
+                        };
+                    }),
+                    offset: 0
                 }
             }
-        }
-
-        crc = reflect(crc, 16);
-        crc ^= 0xffff;
-        crc &= 0xffff;
-
-        return crc;
-    };
-
-    var senderIndex = function (str) {
-        var nickToHash = str.replace(/_*$/, "").toLowerCase();
-        return qChecksum(nickToHash) & 0xF;
-    };
-
-    return sendercolors[senderIndex(nick)];
-};
-
-var render_buffer_full = function (buffer) {
-    return (
-        "<buffer id='buffer" + buffer.id + "' data-bufferid='" + buffer.id + "' class='selected'>" + (
-            "<h2>" + buffer.network + " – " + buffer.name + "</h2>" +
-            "<article>" + (
-                buffer.contexts.map(render_context).join("") +
-                "<inline-button class='load_more'>Load More Results</inline-button>"
-            ) + "</article>"
-        ) + "</buffer>"
-    )
-};
 
-var render_buffer_overview = function (buffer) {
-    return (
-        "<buffer id='buffer" + buffer.id + "' data-bufferid='" + buffer.id + "'>" + (
-            "<h2>" + buffer.network + " – " + buffer.name + "</h2>" +
-            "<article>" + (
-                buffer.contexts.slice(0, 4).map(render_context).join("") +
-                "<inline-button class='load_more'>Show More Results</inline-button>"
-            ) + "</article>"
-        ) + "</buffer>"
-    )
-};
-
-var render_buffer = function (buffer) {
-    return (buffer.selected) ? render_buffer_full(buffer) : render_buffer_overview(buffer);
-};
-
-var attach_buffer = function (elem) {
-    elem.unbind();
-    var id = elem.data("bufferid");
-    elem.click(wrap_click_handler(make_toggle_buffer(id)));
-    elem.find(".load_more").click(wrap_click_handler(function (e) {
-        if (buffers[id].selected || buffers[id].contexts.length <= 4)
-            more_buffer(id);
-        select_buffer(id);
-        e.stopPropagation();
-    }));
-    buffers[id].contexts.forEach(function (context) {
-        var ctx = elem.find("#context" + context.id);
-        if (ctx.length)
-            attach_context(ctx);
-    })
-};
-
-var render_context_overview = function (context) {
-    return (
-        "<context id='context" + context.id + "' data-contextid='" + context.id + "' data-bufferid='" + context.buffer + "'>" + (
-            render_message(context.original, true, true)
-        ) + "</context>"
-    )
-};
+            render.buffer.update(buffer.bufferid);
+        }
 
-var render_context_full = function (context) {
-    return (
-        "<context id='context" + context.id + "' data-contextid='" + context.id + "' data-bufferid='" + context.buffer + "' class='selected'>" + (
-            "<inline-button class='load_before'>Load Earlier Context</inline-button>" + (
-                context.before.map(render_message).join("") +
-                render_message(context.original, true) +
-                context.after.map(render_message).join("")
-            ) + "<inline-button class='load_after'>Load Later Context</inline-button>"
-        ) + "</context>"
-    )
-};
+        if (ids.length == 0)
+            render.no_more();
+    },
+    buffer: {
+        auto: function (buffer) {
+            return (buffer.selected) ? render.buffer.full(buffer) : render.buffer.overview(buffer);
+        },
+        overview: function (buffer) {
+            return (
+                "<buffer id='buffer" + buffer.id + "' data-bufferid='" + buffer.id + "'>" + (
+                    "<h2>" + buffer.network + " – " + buffer.name + "</h2>" +
+                    "<article>" + (
+                        buffer.contexts.slice(0, 4).map(render.context.auto).join("") +
+                        "<inline-button class='load_more'>" + (buffer.contexts.length > 4 ? translation.results.show_more : translation.results.load_more) + "</inline-button>"
+                    ) + "</article>"
+                ) + "</buffer>"
+            )
+        },
+        full: function (buffer) {
+            return (
+                "<buffer id='buffer" + buffer.id + "' data-bufferid='" + buffer.id + "' class='selected'>" + (
+                    "<h2>" + buffer.network + " – " + buffer.name + "</h2>" +
+                    "<article>" + (
+                        buffer.contexts.map(render.context.auto).join("") +
+                        "<inline-button class='load_more'>" + translation.results.load_more + "</inline-button>"
+                    ) + "</article>"
+                ) + "</buffer>"
+            )
+        },
+        update: function (id) {
+            var renderedBuffer = render.buffer.auto(state.buffers[id]);
+            if ($("#buffer" + id).length)
+                $("#buffer" + id).replaceWith(renderedBuffer);
+            else
+                $("#results").append(renderedBuffer);
+
+            render.buffer.attach($("#buffer" + id));
+        },
+        attach: function (elem) {
+            elem.unbind();
+            var id = elem.data("bufferid");
+            elem.click(make_toggle_buffer(id));
+            elem.find(".load_more").click(function (e) {
+                e.stopPropagation();
+
+                if (state.buffers[id].selected || state.buffers[id].contexts.length <= 4)
+                    load.buffer.more(id);
+
+                deselect_buffers(id);
+                state.open.push(make_toggle_buffer(id));
+                state.buffers[id].selected = true;
+                render.buffer.update(id);
+            });
+            state.buffers[id].contexts.forEach(function (context) {
+                var ctx = elem.find("#context" + context.id);
+                if (ctx.length)
+                    render.context.attach(ctx);
+            })
+        }
+    },
+    context: {
+        auto: function (context) {
+            return context.selected ? render.context.full(context) : render.context.overview(context);
+        },
+        overview: function (context) {
+            return (
+                "<context id='context" + context.id + "' data-contextid='" + context.id + "' data-bufferid='" + context.buffer + "'>" + (
+                    render.message(context.original, true, true)
+                ) + "</context>"
+            )
+        },
+        full: function (context) {
+            return (
+                "<context id='context" + context.id + "' data-contextid='" + context.id + "' data-bufferid='" + context.buffer + "' class='selected'>" + (
+                    "<inline-button class='load_before'>" + translation.context.load_earlier + "</inline-button>" + (
+                        context.before.map(render.message).join("") +
+                        render.message(context.original, true) +
+                        context.after.map(render.message).join("")
+                    ) + "<inline-button class='load_after'>" + translation.context.load_later + "</inline-button>"
+                ) + "</context>"
+            )
+        },
+        attach: function (elem) {
+            elem.unbind();
+            var id = elem.data("contextid");
+            var bufferid = elem.data("bufferid");
+            if (state.buffers[bufferid] === undefined) {
+                console.log("Undefined buffer: " + bufferid);
+            }
 
-var render_history_item = function (id, query) {
-    return (
-        "<li id='history" + id + "' data-query='" + btoa(query) + "'>" + (
-            "<span class='icon'>history</span>" +
-            query
-        ) + "</li>"
-    )
-};
+            elem.click(function (e) {
+                e.stopPropagation();
+            });
+            $("#message" + state.buffers[bufferid].contexts[id].original.messageid).click(make_toggle_context(bufferid, id));
+            elem.find(".load_before").click(function (e) {
+                e.stopPropagation();
 
-var update_history = function () {
-    var container = $("#autocomplete ul");
-    container.children().remove();
+                load.context.earlier(bufferid, id, 5);
+            });
+            elem.find(".load_after").click(function (e) {
+                e.stopPropagation();
 
-    var history = get_history().reverse();
-    for (var i = 0; i < history.length; i++) {
-        container.append(render_history_item(i, history[i]));
-        attach_history_item($("#history" + i));
-    }
-    if (history.length == 0) {
-        container.append("<p>No search history available</p>");
+                load.context.later(bufferid, id, 5);
+            });
+        }
+    },
+    message: function (message, highlight, preview) {
+        var content = preview === true ? message.preview : message.message;
+        return (
+            "<message id='message" + message.messageid + "' data-messageid='" + message.messageid + "' " + (highlight === true ? "" : "class='faded'") + ">" + (
+                "<time>" + new Date(message.time.replace(" ", "T") + "Z").toLocaleString() + "</time>" +
+                "<sender style='color: " + sendercolor(message.sender.split("!")[0]) + "'>" + message.sender.split("!")[0] + "</sender>" +
+                "<content>" + content + "</content>"
+            ) + "</message>"
+        )
+    },
+    history: {
+        all: function (history) {
+            var container = $("#autocomplete ul");
+            container.children().remove();
+
+            for (var i = 0; i < history.length; i++) {
+                container.append(render.history.item(i, history[i]));
+                render.history.attach($("#history" + i));
+            }
+            if (history.length == 0) {
+                container.append("<p>" + translation.history.error_unavailable + "</p>");
+            }
+        },
+        item: function (id, query) {
+            return (
+                "<li id='history" + id + "' data-query='" + btoa(query) + "'>" + (
+                    "<span class='icon'>history</span>" +
+                    query
+                ) + "</li>"
+            )
+        },
+        attach: function (elem) {
+            elem.unbind();
+            var query = atob(elem.data("query"));
+            elem.click(function (e) {
+                e.stopPropagation();
+
+                $("#q").val(query);
+                search();
+            });
+        }
+    },
+    loader: function () {
+        $("#results").append("  <div class='loader'><svg class='circular' viewBox='25 25 50 50'><circle class='path' cx='50' cy='50' r='20' fill='none' stroke-width='4' stroke-miterlimit='10'/></svg></div>");
+    },
+    no_more: function () {
+        $("#results").append("<div id='no_more'><img src='res/error.png'><h2>No results</h2></div>");
     }
 };
 
-var attach_history_item = function (elem) {
-    elem.unbind();
-    var query = atob(elem.data("query"));
-    elem.click(wrap_click_handler(function (e) {
-        $("#q").val(query);
-        search();
-    }));
-};
-
-var render_context = function (context) {
-    return context.selected ? render_context_full(context) : render_context_overview(context);
-};
-
-var attach_context = function (elem) {
-    elem.unbind();
-    var id = elem.data("contextid");
-    var bufferid = elem.data("bufferid");
-    if (buffers[bufferid] === undefined) {
-        console.log("Undefined buffer: " + bufferid);
-    }
-
-    elem.click(wrap_click_handler(function (e) {
-        e.stopPropagation();
-    }));
-    $("#message" + buffers[bufferid].contexts[id].original.messageid).click(wrap_click_handler(make_toggle_context(bufferid, id)));
-    elem.find(".load_before").click(wrap_click_handler(function (e) {
-        earlier(bufferid, id, 5);
-        e.stopPropagation();
-    }));
-    elem.find(".load_after").click(wrap_click_handler(function (e) {
-        later(bufferid, id, 5);
-        e.stopPropagation();
-    }));
-};
-
-var render_message = function (message, highlight, preview) {
-    var content = preview === true ? message.preview : message.message;
-    return (
-        "<message id='message" + message.messageid + "' data-messageid='" + message.messageid + "' " + (highlight === true ? "" : "class='faded'") + ">" + (
-            "<time>" + new Date(message.time.replace(" ", "T") + "Z").toLocaleString() + "</time>" +
-            "<sender style='color: " + sendercolor(message.sender.split("!")[0]) + "'>" + message.sender.split("!")[0] + "</sender>" +
-            "<content>" + content + "</content>"
-        ) + "</message>"
-    )
-};
-
-var load_search_overview = function (query, callback) {
-    $.post("web/search/?" + $.param({"query": query}), callback, "json");
-};
-
-var load_search_buffer = function (query, buffer, offset, limit, callback) {
-    $.post("web/searchbuffer/?" + $.param({
-            "query": query,
-            "buffer": buffer,
-            "offset": offset,
-            "limit": limit
-        }), callback, "json");
-};
-
-var load_context = function (msg, buffer, before, after, callback) {
-    $.post("web/backlog/?" + $.param({
-            "anchor": msg,
-            "buffer": buffer,
-            "before": before,
-            "after": after
-        }), callback, "json");
-};
-
-var show_overview = function (ids) {
-    $("#results").children().remove();
-
-    for (var i = 0; i < ids.length; i++) {
-        var buffer = ids[i];
-
-        if (!buffers.hasOwnProperty(buffer.bufferid)) {
-            var ctx = 0;
-            buffers[buffer.bufferid] = {
-                id: buffer.bufferid,
-                name: buffer.buffername,
-                network: buffer.networkname,
-                selected: false,
-                contexts: buffer.messages.map(function (message) {
+var load = {
+    overview: function (query, callback) {
+        $.post("web/search/?" + $.param({"query": query}), callback, "json");
+    },
+    buffer: {
+        raw: function (query, buffer, offset, limit, callback) {
+            $.post("web/searchbuffer/?" + $.param({
+                    "query": query,
+                    "buffer": buffer,
+                    "offset": offset,
+                    "limit": limit
+                }), callback, "json");
+        },
+        more: function (id, limit) {
+            if (limit === undefined)
+                limit = 10;
+
+            load.buffer.raw(state.query, id, state.buffers[id].contexts.length, limit, function (data) {
+                var ctx = state.buffers[id].contexts.length;
+                state.buffers[id].contexts = state.buffers[id].contexts.concat(data.map(function (message) {
                     return {
                         "selected": false,
                         "original": message,
                         "before": [],
                         "after": [],
-                        "buffer": buffer.bufferid,
+                        "buffer": id,
                         "id": ctx++
                     };
-                }),
-                offset: 0
-            }
+                }));
+                render.buffer.update(id);
+            });
+        }
+    },
+    context: {
+        raw: function (msg, buffer, before, after, callback) {
+            $.post("web/backlog/?" + $.param({
+                    "anchor": msg,
+                    "buffer": buffer,
+                    "before": before,
+                    "after": after
+                }), callback, "json");
+        },
+        earlier: function (bufferid, contextid, amount) {
+            var buffer = state.buffers[bufferid];
+            var context = buffer.contexts[contextid];
+            var earliest = (context.before[0] || context.original).messageid;
+            load.context.raw(earliest, bufferid, amount, 0, function (messages) {
+                var newmsgs = messages.slice(0, messages.length - 1);
+                context.before = newmsgs.concat(context.before);
+                render.buffer.update(bufferid);
+            })
+        },
+        later: function (bufferid, contextid, amount) {
+            var buffer = state.buffers[bufferid];
+            var context = buffer.contexts[contextid];
+            var latest = (context.after[context.after.length - 1] || context.original).messageid;
+            load.context.raw(latest, bufferid, 0, amount, function (messages) {
+                var newmsgs = messages.slice(1);
+                context.after = context.after.concat(newmsgs);
+                render.buffer.update(bufferid);
+            })
         }
-
-        update_overview(buffer.bufferid);
     }
-
-    if (ids.length == 0)
-        show_no_more_msg();
-};
-
-var update_overview = function (id) {
-    var renderedBuffer = render_buffer(buffers[id]);
-    if ($("#buffer" + id).length)
-        $("#buffer" + id).replaceWith(renderedBuffer);
-    else
-        $("#results").append(renderedBuffer);
-
-    attach_buffer($("#buffer" + id));
 };
 
 var search = function () {
     var results = $("#results");
     results.children().remove();
     $("#q").blur();
-    buffers = {};
-    open = [];
-    results.click(wrap_click_handler(deselect_buffers));
+    results.click(deselect_buffers);
     state = {
         "query": $("#q").val(),
-        "selected_history_entry": -1
+        "selected_history_entry": -1,
+        "buffers": {},
+        "open": []
     };
-    location = "#" + encodeURIComponent(state.query);
+    window.location = "#" + encodeURIComponent(state.query);
     if (state.query) {
-        show_loader();
-        load_search_overview(state.query, show_overview);
-        add_to_history(state.query);
-        update_history();
+        render.loader();
+        load.overview(state.query, render.overview);
+        search_history.push(state.query);
+        render.history.all(search_history.get());
     }
 };
 
-var more_buffer = function (id, limit) {
-    if (limit === undefined)
-        limit = 10;
-
-    load_search_buffer(state.query, id, buffers[id].contexts.length, limit, function (data) {
-        var ctx = buffers[id].contexts.length;
-        buffers[id].contexts = buffers[id].contexts.concat(data.map(function (message) {
-            return {"selected": false, "original": message, "before": [], "after": [], "buffer": id, "id": ctx++};
-        }));
-        update_overview(id);
-    });
-};
-
 var deselect_buffers = function (except) {
-    $.each(buffers, function (key, buffer) {
-        if (key !== except && buffers[key].selected) {
-            buffers[key].selected = false;
+    $.each(state.buffers, function (key, buffer) {
+        if (key !== except && buffer.selected) {
+            buffer.selected = false;
             unselect_contexts(key);
-            update_buffer(key);
+            render.buffer.update(key);
         }
     })
 };
 
 var unselect_contexts = function (bufferid) {
-    buffers[bufferid].contexts = buffers[bufferid].contexts.map(function (context) {
+    state.buffers[bufferid].contexts = state.buffers[bufferid].contexts.map(function (context) {
         context.selected = false;
         return context
     })
@@ -378,108 +319,97 @@ var unselect_contexts = function (bufferid) {
 
 var make_toggle_buffer = function (id) {
     return function (e) {
-        if (buffers[id].selected) {
+        e.stopPropagation();
+
+        if (state.buffers[id].selected) {
             deselect_buffers();
-            open.pop();
-            buffers[id].selected = false;
+            state.open.pop();
+            state.buffers[id].selected = false;
         } else {
             deselect_buffers();
-            open.push(make_toggle_buffer(id));
-            buffers[id].selected = true;
+            state.open.push(make_toggle_buffer(id));
+            state.buffers[id].selected = true;
         }
-        update_buffer(id);
-        e.stopPropagation();
+        render.buffer.update(id);
     }
 };
 
-var select_buffer = function (id) {
-    deselect_buffers(id);
-    open.push(make_toggle_buffer(id));
-    buffers[id].selected = true;
-    update_buffer(id);
-};
-
-var update_buffer = function (id) {
-    $("#buffer" + id).replaceWith(render_buffer(buffers[id]));
-    attach_buffer($("#buffer" + id));
-};
-
 var make_toggle_context = function (buffer, id) {
     return function (e) {
-        var context = buffers[buffer].contexts[id];
+        e.stopPropagation();
+
+        var context = state.buffers[buffer].contexts[id];
         if (context.selected) {
             unselect_contexts(buffer);
             context.selected = false;
-            open.pop();
+            state.open.pop();
         } else {
             unselect_contexts(buffer);
-            if (!buffers[buffer].selected) {
-                open.push(make_toggle_buffer(buffer));
-                buffers[buffer].selected = true;
+            if (!state.buffers[buffer].selected) {
+                state.open.push(make_toggle_buffer(buffer));
+                state.buffers[buffer].selected = true;
             }
             context.selected = true;
-            open.push(make_toggle_context(buffer, id));
-            if (context.before.length === 0) earlier(buffer, id, 5);
-            if (context.after.length === 0) later(buffer, id, 5);
+            state.open.push(make_toggle_context(buffer, id));
+            if (context.before.length === 0) load.context.earlier(buffer, id, 5);
+            if (context.after.length === 0) load.context.later(buffer, id, 5);
         }
-        update_buffer(buffer);
-        e.stopPropagation();
+        render.buffer.update(buffer);
     }
 };
 
-var show_no_more_msg = function () {
-    $("#results").append("<div id='no_more'><img src='res/error.png'><h2>No results</h2></div>");
-};
-
-var show_loader = function () {
-    $("#results").append("  <div class='loader'><svg class='circular' viewBox='25 25 50 50'><circle class='path' cx='50' cy='50' r='20' fill='none' stroke-width='4' stroke-miterlimit='10'/></svg></div>");
-};
-
-var earlier = function (bufferid, contextid, amount) {
-    var buffer = buffers[bufferid];
-    var context = buffer.contexts[contextid];
-    var earliest = (context.before[0] || context.original).messageid;
-    load_context(earliest, bufferid, amount, 0, function (messages) {
-        var newmsgs = messages.slice(0, messages.length - 1);
-        context.before = sort_messages(newmsgs.concat(context.before));
-        update_buffer(bufferid);
-    })
-};
-
-var later = function (bufferid, contextid, amount) {
-    var buffer = buffers[bufferid];
-    var context = buffer.contexts[contextid];
-    var latest = (context.after[context.after.length - 1] || context.original).messageid;
-    load_context(latest, bufferid, 0, amount, function (messages) {
-        var newmsgs = messages.slice(1);
-        context.after = sort_messages(context.after.concat(newmsgs));
-        update_buffer(bufferid);
-    })
+var hashChange = function () {
+    $("#q").val(decodeURIComponent(location.hash.substr(1)));
+    search();
 };
 
-var sort_messages = function (arr) {
-    return arr.sort(function (x, y) {
-        return x.messageid - y.messageid;
-    }).filter(function (item, pos, ary) {
-        return !pos || item.messageid != ary[pos - 1].messageid;
+var init = function () {
+    $("body").on("click", function (e) {
+        if (state.open.length)
+            state.open[state.open.length - 1](e);
     });
-};
 
-update_history();
+    $("nav").on("click", function (e) {
+        e.stopPropagation();
+    });
 
+    $(window).on("hashchange", hashChange);
 
-$("body").click(function (e) {
-    if (open.length)
-        open[open.length - 1](e);
-});
+    $("#q").on("keypress", function (e) {
+        var key = e.which || e.keyCode;
+        if (key === 13) {
+            search();
+        }
+        if (key === 40) {
+            $("#history" + state.selected_history_entry).removeClass("selected");
+            state.selected_history_entry = (state.selected_history_entry + 1) % get_history().length;
+            $("#q").val(get_history().reverse()[state.selected_history_entry]);
+            $("#history" + state.selected_history_entry).addClass("selected");
+        } else if (key === 38) {
+            $("#history" + state.selected_history_entry).removeClass("selected");
+            if (state.selected_history_entry === 0) {
+                state.selected_history_entry = -1;
+                $("#q").val("");
+            } else {
+                state.selected_history_entry = (state.selected_history_entry - 1) % get_history().length;
+                $("#q").val(get_history().reverse()[state.selected_history_entry]);
+                $("#history" + state.selected_history_entry).addClass("selected");
+            }
+        }
+    });
 
-$("nav").click(function (e) {
-    e.stopPropagation();
-});
+    $("#q").on("focus", function () {
+        $("#autocomplete").addClass("active");
+        $("#results").addClass("hidden");
+        $("nav").addClass("search");
+    });
+    $("#q").on("blur", function () {
+        $("#autocomplete").removeClass("active");
+        $("#results").removeClass("hidden");
+        $("nav").removeClass("search");
+    });
 
-var hashChange = function () {
-    $("#q").val(decodeURIComponent(location.hash.substr(1)));
-    search();
+    hashChange();
+    render.history.all(search_history.get());
 };
-hashChange();
-$(window).on("hashchange", hashChange);
+init();
\ No newline at end of file
diff --git a/res/sendercolor.js b/res/sendercolor.js
new file mode 100644
index 0000000000000000000000000000000000000000..a1011481cd0ff617b4d4955639e5e77149df72f6
--- /dev/null
+++ b/res/sendercolor.js
@@ -0,0 +1,64 @@
+const sendercolor = function (nick) {
+    var sendercolors = [
+        "#e90d7f",
+        "#8e55e9",
+        "#b30e0e",
+        "#17b339",
+        "#58afb3",
+        "#9d54b3",
+        "#b39775",
+        "#3176b3",
+        "#e90d7f",
+        "#8e55e9",
+        "#b30e0e",
+        "#17b339",
+        "#58afb3",
+        "#9d54b3",
+        "#b39775",
+        "#3176b3"
+    ];
+
+    var reflect = function (crc, n) {
+        var j = 1, crcout = 0;
+        for (var i = (1 << (n - 1)); i > 0; i >>= 1) {
+            if ((crc & i) > 0) {
+                crcout |= j;
+            }
+            j <<= 1;
+        }
+        return crcout;
+    };
+
+    var qChecksum = function (str) {
+        var crc = 0xffff;
+        var crcHighBitMask = 0x8000;
+
+        for (var i = 0; i < str.length; i++) {
+            var b = str.codePointAt(i);
+            var c = reflect(b, 8);
+            for (var j = 0x80; j > 0; j >>= 1) {
+                var highBit = crc & crcHighBitMask;
+                crc <<= 1;
+                if ((c & j) > 0) {
+                    highBit ^= crcHighBitMask;
+                }
+                if (highBit > 0) {
+                    crc ^= 0x1021;
+                }
+            }
+        }
+
+        crc = reflect(crc, 16);
+        crc ^= 0xffff;
+        crc &= 0xffff;
+
+        return crc;
+    };
+
+    var senderIndex = function (str) {
+        var nickToHash = str.replace(/_*$/, "").toLowerCase();
+        return qChecksum(nickToHash) & 0xF;
+    };
+
+    return sendercolors[senderIndex(nick)];
+};
\ No newline at end of file
diff --git a/res/translations.js b/res/translations.js
new file mode 100644
index 0000000000000000000000000000000000000000..a963c48e24621c2c3344b80d43da971293a53308
--- /dev/null
+++ b/res/translations.js
@@ -0,0 +1,15 @@
+var translations = {};
+
+translations.en = {
+    results: {
+        show_more: "Show More Results",
+        load_more: "Load More Results"
+    },
+    context: {
+        load_later: "Load Later Context",
+        load_earlier: "Load Earlier Context"
+    },
+    history: {
+        error_unavailable: "No search history available"
+    }
+};
\ No newline at end of file
diff --git a/templates/search.phtml b/templates/search.phtml
index 54dc6e59fa449c2f243834c585814c69fc6a9f6b..62b882bb16ec0619c8286a09c19a4072b8780d9e 100644
--- a/templates/search.phtml
+++ b/templates/search.phtml
@@ -32,6 +32,8 @@
 <div id="bg"></div>
 
 <script src="res/jquery.js"></script>
+<script src="res/translations.js"></script>
+<script src="res/sendercolor.js"></script>
 <script src="res/search.js"></script>
 </body>
 </html>