aboutsummaryrefslogtreecommitdiffstats
path: root/static
diff options
context:
space:
mode:
authorlolcat <will@lolcat.ca>2025-08-11 01:55:15 +0000
committerlolcat <will@lolcat.ca>2025-08-11 01:55:15 +0000
commitcdf958d29333d448f4521f4d2faa2592b58e9b27 (patch)
tree528f2a0ffa789a6f4279d9f54a4a2aaf391f390f /static
downloadshittyweb-search-cdf958d29333d448f4521f4d2faa2592b58e9b27.tar.gz
shittyweb-search-cdf958d29333d448f4521f4d2faa2592b58e9b27.tar.bz2
shittyweb-search-cdf958d29333d448f4521f4d2faa2592b58e9b27.zip
fix wikipedia crashgrafted
Diffstat (limited to 'static')
-rw-r--r--static/404.pngbin0 -> 216 bytes
-rw-r--r--static/client.js985
-rw-r--r--static/icon/amazon.pngbin0 -> 1223 bytes
-rw-r--r--static/icon/appstore.pngbin0 -> 1763 bytes
-rw-r--r--static/icon/call.pngbin0 -> 2155 bytes
-rw-r--r--static/icon/directions.pngbin0 -> 1673 bytes
-rw-r--r--static/icon/facebook.pngbin0 -> 555 bytes
-rw-r--r--static/icon/gamespot.pngbin0 -> 1663 bytes
-rw-r--r--static/icon/github.pngbin0 -> 508 bytes
-rw-r--r--static/icon/googleplay.pngbin0 -> 1046 bytes
-rw-r--r--static/icon/imdb.pngbin0 -> 1259 bytes
-rw-r--r--static/icon/instagram.pngbin0 -> 1804 bytes
-rw-r--r--static/icon/itunes.pngbin0 -> 1721 bytes
-rw-r--r--static/icon/microsoft.pngbin0 -> 1218 bytes
-rw-r--r--static/icon/quora.pngbin0 -> 835 bytes
-rw-r--r--static/icon/reddit.pngbin0 -> 1086 bytes
-rw-r--r--static/icon/rottentomatoes.pngbin0 -> 468 bytes
-rw-r--r--static/icon/sciencedirect.pngbin0 -> 354 bytes
-rw-r--r--static/icon/soundcloud.pngbin0 -> 592 bytes
-rw-r--r--static/icon/spotify.pngbin0 -> 689 bytes
-rw-r--r--static/icon/steam.pngbin0 -> 622 bytes
-rw-r--r--static/icon/twitter.pngbin0 -> 698 bytes
-rw-r--r--static/icon/w3html.pngbin0 -> 1000 bytes
-rw-r--r--static/icon/website.pngbin0 -> 1792 bytes
-rw-r--r--static/icon/wikipedia.pngbin0 -> 1068 bytes
-rw-r--r--static/icon/youtube.pngbin0 -> 1316 bytes
-rw-r--r--static/misc/christmas-dark-bg.pngbin0 -> 115320 bytes
-rw-r--r--static/misc/christmas-hat.pngbin0 -> 13077 bytes
-rw-r--r--static/misc/christmas-white-bg.pngbin0 -> 109889 bytes
-rw-r--r--static/misc/snow.pngbin0 -> 192815 bytes
-rw-r--r--static/serverping.js473
-rw-r--r--static/style.css1384
-rw-r--r--static/themes/Catppuccin Latte.css20
-rw-r--r--static/themes/Catppuccin Mocha.css20
-rw-r--r--static/themes/Cream.css31
-rw-r--r--static/themes/Dark Christmas.css95
-rw-r--r--static/themes/Gore's shitty theme.css322
-rw-r--r--static/themes/Kuuro.css17
-rw-r--r--static/themes/White Christmas.css94
-rw-r--r--static/themes/Wine.css40
-rw-r--r--static/themes/gentoo.css20
41 files changed, 3501 insertions, 0 deletions
diff --git a/static/404.png b/static/404.png
new file mode 100644
index 0000000..e8588cf
--- /dev/null
+++ b/static/404.png
Binary files differ
diff --git a/static/client.js b/static/client.js
new file mode 100644
index 0000000..5935f92
--- /dev/null
+++ b/static/client.js
@@ -0,0 +1,985 @@
+
+/*
+ Global functions
+*/
+function htmlspecialchars(str){
+
+ var map = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#039;'
+ }
+
+ return str.replace(/[&<>"']/g, function(m){return map[m];});
+}
+
+function htmlspecialchars_decode(str){
+
+ var map = {
+ '&amp;': '&',
+ '&lt;': '<',
+ '&gt;': '>',
+ '&quot;': '"',
+ '&#039;': "'"
+ }
+
+ return str.replace(/&amp;|&lt;|&gt;|&quot;|&#039;/g, function(m){return map[m];});
+}
+
+function is_click_within(elem, classname, is_id = false){
+
+ while(true){
+
+ if(elem === null){
+
+ return false;
+ }
+
+ if(
+ (
+ is_id === false &&
+ elem.className == classname
+ ) ||
+ (
+ is_id === true &&
+ elem.id == classname
+ )
+ ){
+
+ return elem;
+ }
+
+ elem = elem.parentElement;
+ }
+}
+
+
+
+/*
+ Prevent GET parameter pollution
+*/
+var form = document.getElementsByTagName("form");
+
+if(
+ form.length !== 0 &&
+ window.location.pathname != "/" &&
+ window.location.pathname != "/settings.php" &&
+ window.location.pathname != "/settings"
+){
+ form = form[0];
+
+ var scraper_dropdown = document.getElementsByName("scraper")[0];
+
+ scraper_dropdown.addEventListener("change", function(choice){
+
+ submit(form);
+ });
+
+ form.addEventListener("submit", function(e){
+
+ e.preventDefault();
+ submit(e.srcElement);
+ });
+}
+
+function submit(e){
+
+ var GET = "";
+ var first = true;
+
+ if((s = document.getElementsByName("s")).length !== 0){
+
+ GET += "?s=" + encodeURIComponent(s[0].value).replaceAll("%20", "+");
+ first = false;
+ }
+
+ Array.from(
+ e.getElementsByTagName("select")
+ ).concat(
+ Array.from(
+ e.getElementsByTagName("input")
+ )
+ ).forEach(function(el){
+
+ var firstelem = el.getElementsByTagName("option");
+
+ if(
+ (
+ (
+ firstelem.length === 0 ||
+ firstelem[0].value != el.value
+ ) &&
+ el.name != "" &&
+ el.value != "" &&
+ el.name != "s"
+ ) ||
+ el.name == "scraper" ||
+ el.name == "nsfw"
+ ){
+
+ if(first){
+
+ GET += "?";
+ first = false;
+ }else{
+
+ GET += "&";
+ }
+
+ GET += encodeURIComponent(el.name).replaceAll("%20", "+") + "=" + encodeURIComponent(el.value).replaceAll("%20", "+");
+ }
+ });
+
+ window.location.href = GET;
+}
+
+
+
+/*
+ Hide show more button when it's not needed on answers
+*/
+var answer_div = document.getElementsByClassName("answer");
+
+if(answer_div.length !== 0){
+ answer_div = Array.from(answer_div);
+ var spoiler_button_div = Array.from(document.getElementsByClassName("spoiler-button"));
+
+ // execute on pageload
+ hide_show_more();
+
+ window.addEventListener("resize", hide_show_more);
+
+ function hide_show_more(){
+
+ var height = window.innerWidth >= 1000 ? 600 : 200;
+
+ for(i=0; i<answer_div.length; i++){
+
+ if(answer_div[i].scrollHeight < height){
+
+ spoiler_button_div[i].style.display = "none";
+
+ document.getElementById(spoiler_button_div[i].htmlFor).checked = true;
+ }else{
+
+ spoiler_button_div[i].style.display = "block";
+ }
+ }
+ }
+}
+
+switch(document.location.pathname){
+
+ case "/web":
+ case "/web.php":
+ var image_class = "image";
+ break;
+
+ case "/images":
+ case "/images.php":
+ var image_class = "thumb";
+ break;
+
+ default:
+ var image_class = null;
+}
+
+if(image_class !== null){
+
+ /*
+ Add popup to document
+ */
+ var popup_bg = document.createElement("div");
+ popup_bg.id = "popup-bg";
+ document.body.appendChild(popup_bg);
+
+ // enable/disable pointer events
+ if(!document.cookie.includes("bg_noclick=yes")){
+
+ popup_bg.style.pointerEvents = "none";
+ }
+
+ var popup_status = document.createElement("div");
+ popup_status.id = "popup-status";
+ document.body.appendChild(popup_status);
+
+ var popup_body = document.createElement("div");
+ popup_body.id = "popup";
+ document.body.appendChild(popup_body);
+
+ // import popup
+ var popup_body = document.getElementById("popup");
+ var popup_status = document.getElementById("popup-status");
+ var popup_image = null; // is set later on popup click
+
+ // image metadata
+ var collection = []; // will contain width, height, image URL
+ var collection_index = 0;
+
+ // event handling helper variables
+ var is_popup_shown = false;
+ var mouse_down = false;
+ var mouse_move = false;
+ var move_x = 0;
+ var move_y = 0;
+ var target_is_popup = false;
+ var mirror_x = false;
+ var mirror_y = false;
+ var rotation = 0;
+
+ /*
+ Image dragging (mousedown)
+ */
+ document.addEventListener("mousedown", function(div){
+
+ if(div.buttons !== 1){
+
+ return;
+ }
+
+ mouse_down = true;
+ mouse_move = false;
+
+ if(is_click_within(div.target, "popup", true) === false){
+
+ target_is_popup = false;
+ }else{
+
+ target_is_popup = true;
+
+ var pos = popup_body.getBoundingClientRect();
+ move_x = div.x - pos.x;
+ move_y = div.y - pos.y;
+ }
+ });
+
+ /*
+ Image dragging (mousemove)
+ */
+ document.addEventListener("mousemove", function(pos){
+
+ if(
+ target_is_popup &&
+ mouse_down
+ ){
+
+ mouse_move = true;
+ movepopup(popup_body, pos.clientX - move_x, pos.clientY - move_y);
+ }
+ });
+
+ /*
+ Image dragging (mouseup)
+ */
+ document.addEventListener("mouseup", function(){
+
+ mouse_down = false;
+ });
+
+ /*
+ Image popup open
+ */
+ document.addEventListener("click", function(click){
+
+ // should our click trigger image open?
+ if(
+ elem = is_click_within(click.target, image_class) ||
+ click.target.classList.contains("openimg")
+ ){
+
+ event.preventDefault();
+ is_popup_shown = true;
+
+ // reset position params
+ mirror_x = false;
+ mirror_y = false;
+ rotation = 0;
+ scale = 60;
+ collection_index = 0;
+
+ // get popup data
+ if(elem === true){
+ // we clicked a simple image preview
+ elem = click.target;
+ var image_url = elem.getAttribute("src");
+
+ if(image_url.startsWith("/proxy")){
+
+ var match = image_url.match(/i=([^&]+)/);
+
+ if(match !== null){
+
+ image_url = decodeURIComponent(match[1]);
+ }
+ }else{
+
+ image_url = htmlspecialchars_decode(image_url);
+ }
+
+ var w = Math.round(click.target.naturalWidth);
+ var h = Math.round(click.target.naturalHeight);
+
+ if(
+ w === 0 ||
+ h === 0
+ ){
+
+ w = 100;
+ h = 100;
+ }
+
+ collection = [
+ {
+ "url": image_url,
+ "width": w,
+ "height": h
+ }
+ ];
+
+ var title = "No description provided";
+
+ if(click.target.title != ""){
+
+ title = click.target.title;
+ }else{
+
+ if(click.target.alt != ""){
+
+ title = click.target.alt;
+ }
+ }
+ }else{
+
+ if(image_class == "thumb"){
+ // we're inside image.php
+
+ elem =
+ elem
+ .parentElement
+ .parentElement;
+
+ var image_url = elem.getElementsByTagName("a")[1].href;
+ }else{
+
+ // we're inside web.php
+ var image_url = elem.href;
+ }
+
+ collection =
+ JSON.parse(
+ elem.getAttribute("data-json")
+ );
+
+ var imagesize = elem.getElementsByTagName("img")[0];
+
+ var imagesize_w = 0;
+ var imagesize_h = 0;
+
+ if(imagesize.complete){
+
+ imagesize_w = imagesize.naturalWidth;
+ imagesize_h = imagesize.naturalHeight;
+ }
+
+ if(
+ imagesize_w === 0 ||
+ imagesize_h === 0
+ ){
+
+ imagesize_w = 100;
+ imagesize_h = 100;
+ }
+
+ for(var i=0; i<collection.length; i++){
+
+ if(collection[i].width === null){
+
+ collection[i].width = imagesize_w;
+ collection[i].height = imagesize_h;
+ }
+ }
+
+ var title = elem.title;
+ }
+
+ // prepare HTML
+ var html =
+ '<div id="popup-num">(' + collection.length + ')</div>' +
+ '<div id="popup-dropdown">' +
+ '<select name="viewer-res" onchange="changeimage(event)">';
+
+ for(i=0; i<collection.length; i++){
+
+ if(collection[i].url.startsWith("data:")){
+
+ var domain = "&lt;Base64 Data&gt;";
+ }else{
+
+ var domain = new URL(collection[i].url).hostname;
+ }
+
+ html += '<option value="' + i + '">' + '(' + collection[i].width + 'x' + collection[i].height + ') ' + domain + '</option>';
+ }
+
+ popup_status.innerHTML =
+ html + '</select></div>' +
+ '<a href="' + htmlspecialchars(image_url) + '" rel="noreferrer nofollow "id="popup-title">' + htmlspecialchars(title) + '</a>';
+
+ popup_body.innerHTML =
+ '<img src="' + getproxylink(collection[0].url) + '" draggable="false" id="popup-image">';
+
+ // make changes to DOM
+ popup_body.style.display = "block";
+ popup_bg.style.display = "block";
+ popup_status.style.display = "table";
+
+ // store for rotation functions & changeimage()
+ popup_image = document.getElementById("popup-image");
+
+ scalepopup(collection[collection_index], scale);
+ centerpopup();
+ }else{
+
+ // click inside the image viewer
+ // resize image
+ if(is_click_within(click.target, "popup", true)){
+
+ if(mouse_move === false){
+ scale = 80;
+ scalepopup(collection[collection_index], scale);
+ centerpopup();
+ }
+ }else{
+
+ if(is_click_within(click.target, "popup-status", true) === false){
+
+ // click outside the popup while its open
+ // close it
+ if(is_popup_shown){
+
+ hidepopup();
+ }
+ }
+ }
+ }
+ });
+
+ /*
+ Scale image viewer
+ */
+ popup_body.addEventListener("wheel", function(scroll){
+
+ event.preventDefault();
+
+ if(
+ scroll.altKey ||
+ scroll.ctrlKey ||
+ scroll.shiftKey
+ ){
+
+ var increment = 7;
+ }else{
+
+ var increment = 14;
+ }
+
+ if(scroll.wheelDelta > 0){
+
+ // scrolling up
+ scale = scale + increment;
+ }else{
+
+ // scrolling down
+ if(scale - increment > 7){
+ scale = scale - increment;
+ }
+ }
+
+ // calculate relative size before scroll
+ var pos = popup_body.getBoundingClientRect();
+ var x = (scroll.x - pos.x) / pos.width;
+ var y = (scroll.y - pos.y) / pos.height;
+
+ scalepopup(collection[collection_index], scale);
+
+ // move popup to % we found
+ pos = popup_body.getBoundingClientRect();
+
+ movepopup(
+ popup_body,
+ scroll.clientX - (x * pos.width),
+ scroll.clientY - (y * pos.height)
+ );
+ });
+
+ /*
+ Keyboard controls
+ */
+
+ document.addEventListener("keydown", function(key){
+
+ // close popup
+ if(
+ is_popup_shown &&
+ key.keyCode === 27
+ ){
+
+ hidepopup();
+ return;
+ }
+
+ if(is_popup_shown === false){
+
+ return;
+ }
+
+ if(
+ key.altKey ||
+ key.ctrlKey ||
+ key.shiftKey
+ ){
+
+ // mirror image
+ switch(key.keyCode){
+
+ case 37:
+ // left
+ key.preventDefault();
+ mirror_x = true;
+ break;
+
+ case 38:
+ // up
+ key.preventDefault();
+ mirror_y = false;
+ break;
+
+ case 39:
+ // right
+ key.preventDefault();
+ mirror_x = false;
+ break;
+
+ case 40:
+ // down
+ key.preventDefault();
+ mirror_y = true;
+ break;
+ }
+ }else{
+
+ // rotate image
+ switch(key.keyCode){
+
+ case 37:
+ // left
+ key.preventDefault();
+ rotation = -90;
+ break;
+
+ case 38:
+ // up
+ key.preventDefault();
+ rotation = 0;
+ break;
+
+ case 39:
+ // right
+ key.preventDefault();
+ rotation = 90;
+ break;
+
+ case 40:
+ // down
+ key.preventDefault();
+ rotation = -180;
+ break;
+ }
+ }
+
+ popup_image.style.transform =
+ "scale(" +
+ (mirror_x ? "-1" : "1") +
+ ", " +
+ (mirror_y ? "-1" : "1") +
+ ") " +
+ "rotate(" +
+ rotation + "deg" +
+ ")";
+ });
+}
+
+function getproxylink(url){
+
+ if(url.startsWith("data:")){
+
+ return htmlspecialchars(url);
+ }else{
+
+ return '/proxy?i=' + encodeURIComponent(url);
+ }
+}
+
+function hidepopup(){
+
+ is_popup_shown = false;
+ popup_status.style.display = "none";
+ popup_body.style.display = "none";
+ popup_bg.style.display = "none";
+}
+
+function scalepopup(size, scale){
+
+ var ratio =
+ Math.min(
+ (window.innerWidth * (scale / 100)) / collection[collection_index].width, (window.innerHeight * (scale / 100)) / collection[collection_index].height
+ );
+
+ popup_body.style.width = size.width * ratio + "px";
+ popup_body.style.height = size.height * ratio + "px";
+}
+
+function centerpopup(){
+
+ var size = popup_body.getBoundingClientRect();
+ var size = {
+ "width": parseInt(size.width),
+ "height": parseInt(size.height)
+ };
+
+ movepopup(
+ popup_body,
+ (window.innerWidth / 2) - (size.width / 2),
+ (window.innerHeight / 2) - (size.height / 2)
+ );
+}
+
+function movepopup(popup_body, x, y){
+
+ popup_body.style.left = x + "px";
+ popup_body.style.top = y + "px";
+}
+
+function changeimage(event){
+
+ // reset rotation params
+ mirror_x = false;
+ mirror_y = false;
+ rotation = 0;
+
+ scale = 60;
+
+ collection_index = parseInt(event.target.value);
+
+ // we set innerHTML otherwise old image lingers a little
+ popup_body.innerHTML =
+ '<img src="' + getproxylink(collection[collection_index].url) + '" draggable="false" id="popup-image">';
+
+ // store for rotation functions & changeimage()
+ popup_image = document.getElementById("popup-image");
+
+ scalepopup(collection[collection_index], scale);
+ centerpopup();
+}
+
+var searchbox_wrapper = document.getElementsByClassName("searchbox");
+
+if(searchbox_wrapper.length !== 0){
+
+ searchbox_wrapper = searchbox_wrapper[0];
+ var searchbox = searchbox_wrapper.getElementsByTagName("input")[1];
+
+ /*
+ Textarea shortcuts
+ */
+ document.addEventListener("keydown", function(key){
+
+ switch(key.keyCode){
+
+ case 191:
+ // 191 = /
+ if(document.activeElement.tagName == "INPUT"){
+
+ // already focused, ignore
+ break;
+ }
+
+ if(
+ typeof is_popup_shown != "undefined" &&
+ is_popup_shown
+ ){
+
+ hidepopup();
+ }
+
+ window.scrollTo(0, 0);
+ searchbox.focus();
+ key.preventDefault();
+ break;
+ }
+ });
+
+ /*
+ Autocompleter
+ */
+ if( // make sure the user wants it
+ document.cookie.includes("scraper_ac=") &&
+ document.cookie.includes("scraper_ac=disabled") === false
+ ){
+
+ var autocomplete_cache = [];
+ var focuspos = -1;
+ var list = [];
+ var autocomplete_div = document.getElementsByClassName("autocomplete")[0];
+
+ if(
+ document.cookie.includes("scraper_ac=auto") &&
+ typeof scraper_dropdown != "undefined"
+ ){
+
+ var ac_req_appendix = "&scraper=" + scraper_dropdown.value;
+ }else{
+
+ var ac_req_appendix = "";
+ }
+
+ function getsearchboxtext(){
+
+ var value =
+ searchbox.value
+ .trim()
+ .replace(
+ / +/g,
+ " "
+ )
+ .toLowerCase();
+
+ return value;
+ }
+
+ searchbox.addEventListener("input", async function(){
+
+ // ratelimit on input only
+ // dont ratelimit if we already have res
+ if(typeof autocomplete_cache[getsearchboxtext()] != "undefined"){
+
+ await getac();
+ }else{
+
+ await getac_ratelimit();
+ }
+ });
+
+ async function getac(){
+
+ var curvalue = getsearchboxtext();
+
+ if(curvalue == ""){
+
+ // hide autocompleter
+ autocomplete_div.style.display = "none";
+ return;
+ }
+
+ if(typeof autocomplete_cache[curvalue] == "undefined"){
+
+ /*
+ Fetch autocomplete
+ */
+ // make sure we dont fetch same thing twice
+ autocomplete_cache[curvalue] = [];
+
+ var res = await fetch("/api/v1/ac?s=" + (encodeURIComponent(curvalue).replaceAll("%20", "+")) + ac_req_appendix);
+ if(!res.ok){
+
+ return;
+ }
+
+ var json = await res.json();
+
+ autocomplete_cache[curvalue] = json[1];
+
+ if(curvalue == getsearchboxtext()){
+
+ render_ac(curvalue, autocomplete_cache[curvalue]);
+ }
+ return;
+ }
+
+ render_ac(curvalue, autocomplete_cache[curvalue]);
+ }
+
+ var ac_func = null;
+ function getac_ratelimit(){
+
+ return new Promise(async function(resolve, reject){
+
+ if(ac_func !== null){
+
+ clearTimeout(ac_func);
+ }//else{
+
+ // no ratelimits
+ //getac();
+ //}
+
+ ac_func =
+ setTimeout(function(){
+
+ ac_func = null;
+ getac(); // get results after 100ms of no keystroke
+ resolve();
+ }, 200);
+ });
+ }
+
+ function render_ac(query, list){
+
+ if(list.length === 0){
+
+ autocomplete_div.style.display = "none";
+ return;
+ }
+
+ html = "";
+
+ // prepare regex
+ var highlight = query.split(" ");
+ var regex = [];
+
+ for(var k=0; k<highlight.length; k++){
+
+ // espace regex
+ regex.push(
+ highlight[k].replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
+ );
+ }
+
+ regex = new RegExp(highlight.join("|"), "gi");
+
+ for(var i=0; i<list.length; i++){
+
+ html +=
+ '<div tabindex="0" class="entry" onclick="handle_entry_click(this);">' +
+ htmlspecialchars(
+ list[i]
+ ).replace(
+ regex,
+ '<u>$&</u>'
+ ) +
+ '</div>';
+ }
+
+ autocomplete_div.innerHTML = html;
+ autocomplete_div.style.display = "block";
+ }
+
+ var should_focus = false;
+ document.addEventListener("keydown", function(event){
+
+ if(event.key == "Escape"){
+
+ document.activeElement.blur();
+ focuspos = -1;
+ autocomplete_div.style.display = "none";
+ return;
+ }
+
+ if(
+ is_click_within(event.target, "searchbox") === false ||
+ typeof autocomplete_cache[getsearchboxtext()] == "undefined"
+ ){
+
+ return;
+ }
+
+ switch(event.key){
+
+ case "ArrowUp":
+ event.preventDefault();
+ focuspos--;
+ if(focuspos === -2){
+
+ focuspos = autocomplete_cache[getsearchboxtext()].length - 1;
+ }
+ break;
+
+ case "ArrowDown":
+ case "Tab":
+ event.preventDefault();
+
+ focuspos++;
+ if(focuspos >= autocomplete_cache[getsearchboxtext()].length){
+
+ focuspos = -1;
+ }
+ break;
+
+ case "Enter":
+ should_focus = true;
+
+ if(focuspos !== -1){
+
+ // replace input content
+ event.preventDefault();
+ searchbox.value =
+ autocomplete_div.getElementsByClassName("entry")[focuspos].innerText;
+ break;
+ }
+ break;
+
+ default:
+ focuspos = -1;
+ break;
+ }
+
+ if(focuspos === -1){
+
+ searchbox.focus();
+ return;
+ }
+
+ autocomplete_div.getElementsByClassName("entry")[focuspos].focus();
+ });
+
+ window.addEventListener("blur", function(){
+
+ autocomplete_div.style.display = "none";
+ });
+
+ document.addEventListener("keyup", function(event){
+
+ // handle ENTER key on entry
+ if(should_focus){
+
+ should_focus = false;
+ searchbox.focus();
+ }
+ });
+
+ document.addEventListener("mousedown", function(event){
+
+ // hide input if click is outside
+ if(is_click_within(event.target, "searchbox") === false){
+
+ autocomplete_div.style.display = "none";
+ return;
+ }
+ });
+
+ function handle_entry_click(event){
+
+ searchbox.value = event.innerText;
+ focuspos = -1;
+ searchbox.focus();
+ }
+
+ searchbox.addEventListener("focus", function(){
+
+ focuspos = -1;
+ getac();
+ });
+ }
+}
diff --git a/static/icon/amazon.png b/static/icon/amazon.png
new file mode 100644
index 0000000..043a7e2
--- /dev/null
+++ b/static/icon/amazon.png
Binary files differ
diff --git a/static/icon/appstore.png b/static/icon/appstore.png
new file mode 100644
index 0000000..85663bb
--- /dev/null
+++ b/static/icon/appstore.png
Binary files differ
diff --git a/static/icon/call.png b/static/icon/call.png
new file mode 100644
index 0000000..c91bece
--- /dev/null
+++ b/static/icon/call.png
Binary files differ
diff --git a/static/icon/directions.png b/static/icon/directions.png
new file mode 100644
index 0000000..699da40
--- /dev/null
+++ b/static/icon/directions.png
Binary files differ
diff --git a/static/icon/facebook.png b/static/icon/facebook.png
new file mode 100644
index 0000000..0ae3137
--- /dev/null
+++ b/static/icon/facebook.png
Binary files differ
diff --git a/static/icon/gamespot.png b/static/icon/gamespot.png
new file mode 100644
index 0000000..3453e07
--- /dev/null
+++ b/static/icon/gamespot.png
Binary files differ
diff --git a/static/icon/github.png b/static/icon/github.png
new file mode 100644
index 0000000..f78f0c0
--- /dev/null
+++ b/static/icon/github.png
Binary files differ
diff --git a/static/icon/googleplay.png b/static/icon/googleplay.png
new file mode 100644
index 0000000..05ffc19
--- /dev/null
+++ b/static/icon/googleplay.png
Binary files differ
diff --git a/static/icon/imdb.png b/static/icon/imdb.png
new file mode 100644
index 0000000..d8ccb2f
--- /dev/null
+++ b/static/icon/imdb.png
Binary files differ
diff --git a/static/icon/instagram.png b/static/icon/instagram.png
new file mode 100644
index 0000000..41d9bf2
--- /dev/null
+++ b/static/icon/instagram.png
Binary files differ
diff --git a/static/icon/itunes.png b/static/icon/itunes.png
new file mode 100644
index 0000000..99a1dff
--- /dev/null
+++ b/static/icon/itunes.png
Binary files differ
diff --git a/static/icon/microsoft.png b/static/icon/microsoft.png
new file mode 100644
index 0000000..46d048a
--- /dev/null
+++ b/static/icon/microsoft.png
Binary files differ
diff --git a/static/icon/quora.png b/static/icon/quora.png
new file mode 100644
index 0000000..00746b3
--- /dev/null
+++ b/static/icon/quora.png
Binary files differ
diff --git a/static/icon/reddit.png b/static/icon/reddit.png
new file mode 100644
index 0000000..6522815
--- /dev/null
+++ b/static/icon/reddit.png
Binary files differ
diff --git a/static/icon/rottentomatoes.png b/static/icon/rottentomatoes.png
new file mode 100644
index 0000000..dd462e7
--- /dev/null
+++ b/static/icon/rottentomatoes.png
Binary files differ
diff --git a/static/icon/sciencedirect.png b/static/icon/sciencedirect.png
new file mode 100644
index 0000000..20cef17
--- /dev/null
+++ b/static/icon/sciencedirect.png
Binary files differ
diff --git a/static/icon/soundcloud.png b/static/icon/soundcloud.png
new file mode 100644
index 0000000..a12ba39
--- /dev/null
+++ b/static/icon/soundcloud.png
Binary files differ
diff --git a/static/icon/spotify.png b/static/icon/spotify.png
new file mode 100644
index 0000000..c7a35ca
--- /dev/null
+++ b/static/icon/spotify.png
Binary files differ
diff --git a/static/icon/steam.png b/static/icon/steam.png
new file mode 100644
index 0000000..ab3e73d
--- /dev/null
+++ b/static/icon/steam.png
Binary files differ
diff --git a/static/icon/twitter.png b/static/icon/twitter.png
new file mode 100644
index 0000000..bce2863
--- /dev/null
+++ b/static/icon/twitter.png
Binary files differ
diff --git a/static/icon/w3html.png b/static/icon/w3html.png
new file mode 100644
index 0000000..2f107db
--- /dev/null
+++ b/static/icon/w3html.png
Binary files differ
diff --git a/static/icon/website.png b/static/icon/website.png
new file mode 100644
index 0000000..aa691f8
--- /dev/null
+++ b/static/icon/website.png
Binary files differ
diff --git a/static/icon/wikipedia.png b/static/icon/wikipedia.png
new file mode 100644
index 0000000..b3e778e
--- /dev/null
+++ b/static/icon/wikipedia.png
Binary files differ
diff --git a/static/icon/youtube.png b/static/icon/youtube.png
new file mode 100644
index 0000000..542d7f0
--- /dev/null
+++ b/static/icon/youtube.png
Binary files differ
diff --git a/static/misc/christmas-dark-bg.png b/static/misc/christmas-dark-bg.png
new file mode 100644
index 0000000..83f7c0a
--- /dev/null
+++ b/static/misc/christmas-dark-bg.png
Binary files differ
diff --git a/static/misc/christmas-hat.png b/static/misc/christmas-hat.png
new file mode 100644
index 0000000..91d96ad
--- /dev/null
+++ b/static/misc/christmas-hat.png
Binary files differ
diff --git a/static/misc/christmas-white-bg.png b/static/misc/christmas-white-bg.png
new file mode 100644
index 0000000..a627775
--- /dev/null
+++ b/static/misc/christmas-white-bg.png
Binary files differ
diff --git a/static/misc/snow.png b/static/misc/snow.png
new file mode 100644
index 0000000..d157188
--- /dev/null
+++ b/static/misc/snow.png
Binary files differ
diff --git a/static/serverping.js b/static/serverping.js
new file mode 100644
index 0000000..6b680d5
--- /dev/null
+++ b/static/serverping.js
@@ -0,0 +1,473 @@
+
+function htmlspecialchars(str){
+
+ if(str === null){
+
+ return "<i>&lt;Empty&gt;</i>";
+ }
+
+ var map = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#039;'
+ }
+
+ return str.replace(/[&<>"']/g, function(m){return map[m];});
+}
+
+// initialize garbage
+var list = [];
+var pinged_list = [];
+var reqs = 0;
+var errors = 0;
+var sort = 6; // highest version first
+
+// check for instance redirect stuff
+var redir = [];
+var target = "/web?";
+new URL(window.location.href)
+ .searchParams
+ .forEach(
+ function(value, key){
+
+ if(key == "target"){
+
+ target = "/" + encodeURIComponent(value) + "?";
+ return;
+ }
+
+ if(key == "npt"){ return; }
+ redir.push(encodeURIComponent(key) + "=" + encodeURIComponent(value))
+ }
+ );
+
+if(redir.length !== 0){
+
+ redir = target + redir.join("&");
+}else{
+
+ redir = "";
+}
+
+var quote = document.createElement("div");
+quote.className = "quote";
+quote.innerHTML = 'Pinged <b>0</b> servers (<b>0</b> failed requests)';
+var [div_servercount, div_failedreqs] =
+ quote.getElementsByTagName("b");
+
+var noscript = document.getElementsByTagName("noscript")[0];
+document.body.insertBefore(quote, noscript.nextSibling);
+
+// create table
+var table = document.createElement("table");
+table.innerHTML =
+ '<thead>' +
+ '<tr>' +
+ '<th class="extend">Server</th>' +
+ '<th>Address</th>' +
+ '<th>Bot protection</th>' +
+ '<th title="Amount of legit requests processed since the last APCU cache clear (usually happens at midnight)">Real reqs (?)</th>' +
+ '<th title="Amount of filtered requests processed since the last APCU cache clear (usually happens at midnight)">Bot reqs (?)</th>' +
+ '<th>API</th>' +
+ '<th><div class="arrow up"></div>Version</th>' +
+ '</tr>' +
+ '</thead>' +
+ '<tbody></tbody>';
+
+document.body.insertBefore(table, quote.nextSibling);
+
+// handle sorting clicks
+var tbody = table.getElementsByTagName("tbody")[0];
+var th = table.getElementsByTagName("th");
+
+for(var i=0; i<th.length; i++){
+
+ th[i].addEventListener("click", function(event){
+
+ if(event.target.className.includes("arrow")){
+
+ var div = event.target.parentElement;
+ }else{
+
+ var div = event.target;
+ }
+
+ var arrow = div.getElementsByClassName("arrow");
+ var orientation = 0; // up
+
+ if(arrow.length === 0){
+
+ // delete arrow and add new one
+ arrow = document.getElementsByClassName("arrow");
+ arrow[0].remove();
+
+ arrow = document.createElement("div");
+ arrow.className = "arrow up";
+ div.insertBefore(arrow, event.target.firstChild);
+ }else{
+
+ // switch arrow position
+ if(arrow[0].className == "arrow down"){
+
+ arrow[0].className = "arrow up";
+ }else{
+
+ arrow[0].className = "arrow down";
+ orientation = 1;
+ }
+ }
+
+ switch(div.textContent.toLowerCase()){
+
+ case "server": sort = 0 + orientation; break;
+ case "address": sort = 2 + orientation; break;
+ case "bot protection": sort = 4 + orientation; break;
+ case "real reqs (?)": sort = 6 + orientation; break;
+ case "bot reqs (?)": sort = 8 + orientation; break;
+ case "api": sort = 10 + orientation; break;
+ case "version": sort = 12 + orientation; break;
+ }
+
+ render_list();
+ });
+}
+
+function validate_url(url, allow_http = false){
+
+ try{
+
+ url = new URL(url);
+ if(
+ url.protocol == "https:" ||
+ (
+ (
+ allow_http === true ||
+ window.location.protocol == "http:"
+ ) &&
+ url.protocol == "http:"
+ )
+ ){
+
+ return true;
+ }
+ }catch(error){} // do nothing
+
+ return false;
+}
+
+function number_format(int){
+
+ return new Intl.NumberFormat().format(int);
+}
+
+// parse initial server list
+fetch_server(window.location.origin);
+
+async function fetch_server(server){
+
+ if(!validate_url(server)){
+ console.warn("Invalid server URL: " + server);
+ return;
+ }
+
+ // make sure baseURL is origin
+ server = new URL(server).origin;
+ // prevent multiple fetches
+ for(var i=0; i<list.length; i++){
+
+ if(list[i] == server){
+
+ // serber was already fetched
+ return;
+ }
+ }
+
+ // prevent future fetches
+ list.push(server);
+
+ var data = null;
+
+ try{
+
+ var payload = await fetch(server + "/ami4get");
+
+ if(payload.status !== 200){
+
+ // endpoint is not available
+ errors++;
+ div_failedreqs.textContent = number_format(errors);
+ console.warn(server + ": Invalid HTTP code " + payload.status);
+ return;
+ }
+
+ data = await payload.json();
+
+ }catch(error){
+
+ errors++;
+ div_failedreqs.textContent = number_format(errors);
+ console.warn(server + ": Could not fetch or decode JSON");
+ return;
+ }
+
+ // sanitize data
+ if(
+ typeof data.status != "string" ||
+ data.status != "ok" ||
+ typeof data.server != "object" ||
+ !(
+ typeof data.server.name == "string" ||
+ (
+ typeof data.server.name == "object" &&
+ data.server.name === null
+ )
+ ) ||
+ typeof data.service != "string" ||
+ data.service != "4get" ||
+ (
+ typeof data.server.description != "string" &&
+ data.server.description !== null
+ ) ||
+ typeof data.server.bot_protection != "number" ||
+ typeof data.server.real_requests != "number" ||
+ typeof data.server.bot_requests != "number" ||
+ typeof data.server.api_enabled != "boolean" ||
+ typeof data.server.alt_addresses != "object" ||
+ typeof data.server.version != "number" ||
+ typeof data.instances != "object"
+ ){
+
+ errors++;
+ div_failedreqs.textContent = number_format(errors);
+ console.warn(server + ": Malformed JSON");
+ return;
+ }
+
+ data.server.ip = server;
+
+ reqs++;
+ div_servercount.textContent = number_format(reqs);
+
+ var total = pinged_list.push(data) - 1;
+ pinged_list[total].index = total;
+
+ render_list();
+
+ // get more serbers
+ for(var i=0; i<data.instances.length; i++){
+
+ fetch_server(data.instances[i]);
+ }
+}
+
+function sorta(object, element, order){
+
+ return object.slice().sort(
+ function(a, b){
+
+ if(order){
+
+ return a.server[element] - b.server[element];
+ }
+
+ return b.server[element] - a.server[element];
+ }
+ );
+}
+
+function textsort(object, element, order){
+
+ var sort = object.slice().sort(
+ function(a, b){
+
+ return a.server[element].localeCompare(b.server[element]);
+ }
+ );
+
+ if(!order){
+ return sort.reverse();
+ }
+
+ return sort;
+}
+
+function render_list(){
+
+ var sorted_list = [];
+
+ // sort
+ var filter = Boolean(sort % 2);
+
+ switch(sort){
+
+ case 0:
+ case 1:
+ sorted_list = textsort(pinged_list, "name", filter === true ? false : true);
+ break;
+
+ case 2:
+ case 3:
+ sorted_list = textsort(pinged_list, "ip", filter === true ? false : true);
+ break;
+
+ case 4:
+ case 5:
+ sorted_list = sorta(pinged_list, "bot_protection", filter === true ? false : true);
+ break;
+
+ case 6:
+ case 7:
+ sorted_list = sorta(pinged_list, "real_requests", filter);
+ break;
+
+ case 8:
+ case 9:
+ sorted_list = sorta(pinged_list, "bot_requests", filter);
+ break;
+
+ case 10:
+ case 11:
+ sorted_list = sorta(pinged_list, "api_enabled", filter);
+ break;
+
+ case 12:
+ case 13:
+ sorted_list = sorta(pinged_list, "version", filter);
+ break;
+ }
+
+ // render tabloid
+ var html = "";
+
+ for(var k=0; k<sorted_list.length; k++){
+
+ html += '<tr onclick="show_server(' + sorted_list[k].index + ');">';
+
+ for(var i=0; i<7; i++){
+
+ html += '<td';
+
+ switch(i){
+
+ // server name
+ case 0: html += ' class="extend">' + htmlspecialchars(sorted_list[k].server.name); break;
+ case 1: html += '>' + htmlspecialchars(new URL(sorted_list[k].server.ip).host); break;
+ case 2: // bot protection
+ switch(sorted_list[k].server.bot_protection){
+
+ case 0:
+ html += '><span style="color:var(--green);">Disabled</span>';
+ break;
+
+ case 1:
+ html += '><span style="color:var(--yellow);">Image captcha</span>';
+ break;
+
+ case 2:
+ html += '><span style="color:var(--red);">Invite only</span>';
+ break;
+
+ default:
+ html += '>Unknown';
+ }
+ break;
+
+ case 3: // real reqs
+ html += '>' + number_format(sorted_list[k].server.real_requests);
+ break;
+
+ case 4: // bot reqs
+ html += '>' + number_format(sorted_list[k].server.bot_requests);
+ break;
+
+ case 5: // api enabled
+
+ if(sorted_list[k].server.api_enabled){
+
+ html += '><span style="color:var(--green);">Yes</span>';
+ }else{
+
+ html += '><span style="color:var(--red);">No</span>';
+ }
+ break;
+
+ // version
+ case 6: html += ">v" + sorted_list[k].server.version; break;
+ }
+
+ html += '</td>';
+ }
+
+ html += '</tr>';
+ }
+
+ console.log(html);
+
+ tbody.innerHTML = html;
+}
+
+var popup_bg = document.getElementById("popup-bg");
+var popup_wrapper = document.getElementsByClassName("popup-wrapper")[0];
+var popup = popup_wrapper.getElementsByClassName("popup")[0];
+var popup_shown = false;
+
+popup_bg.addEventListener("click", function(){
+
+ popup_wrapper.style.display = "none";
+ popup_bg.style.display = "none";
+});
+
+function show_server(serverid){
+
+ var html =
+ '<h2>' + htmlspecialchars(pinged_list[serverid].server.name) + '</h2>' +
+ 'Description' +
+ '<div class="code">' + htmlspecialchars(pinged_list[serverid].server.description) + '</div>';
+
+ var url_obj = new URL(pinged_list[serverid].server.ip);
+ var url = htmlspecialchars(url_obj.origin);
+ var domain = url_obj.hostname;
+
+ html +=
+ 'URL: <a rel="noreferer" target="_BLANK" href="' + url + redir + '">' + url + '</a> <a rel="noreferer" target="_BLANK" href="https://browserleaks.com/ip/' + encodeURIComponent(domain) + '">(IP lookup)</a>' +
+ '<br><br>Alt addresses:';
+
+ var len = pinged_list[serverid].server.alt_addresses.length;
+
+ if(len === 0){
+
+ html += ' <i>&lt;Empty&gt;</i>';
+ }else{
+
+ html += '<ul>';
+
+ for(var i=0; i<len; i++){
+
+ var url_obj = new URL(pinged_list[serverid].server.alt_addresses[i]);
+ var url = htmlspecialchars(url_obj.origin);
+ var domain = url_obj.hostname;
+
+ if(validate_url(pinged_list[serverid].server.alt_addresses[i], true)){
+
+ html += '<li><a rel="noreferer" href="' + url + redir + '" target="_BLANK">' + url + '</a> <a rel="noreferer" target="_BLANK" href="https://browserleaks.com/ip/' + encodeURIComponent(domain) + '">(IP lookup)</a></li>';
+ }else{
+
+ console.warn(pinged_list[serverid].server.ip + ": Invalid peer URL => " + pinged_list[serverid].server.alt_addresses[i]);
+ }
+ }
+
+ html += '</ul>';
+ }
+ popup.innerHTML = html;
+
+ popup_wrapper.style.display = "block";
+ popup_bg.style.display = "block";
+}
+
+function hide_server(){
+
+ popup_wrapper.style.display = "none";
+ popup_bg.style.display = "none";
+}
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..6276417
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,1384 @@
+:root{
+ /* background */
+ --1d2021: #1d2021;
+ --282828: #282828;
+ --3c3836: #3c3836;
+ --504945: #504945;
+
+ /* font */
+ --928374: #928374;
+ --a89984: #a89984;
+ --bdae93: #bdae93;
+ --8ec07c: #8ec07c;
+ --ebdbb2: #ebdbb2;
+
+ /* code highlighter */
+ --comment: #9e8e73;
+ --default: #d4be98;
+ --keyword: #d8a657;
+ --string: #7daea7;
+
+ /* color codes for instance list */
+ --green: #b8bb26;
+ --yellow: #d8a657;
+ --red: #fb4934;
+}
+
+audio{
+ max-width:100%;
+ display:block;
+}
+
+.left audio{
+ margin-top:7px;
+}
+
+.right-wrapper audio{
+ margin-bottom:17px;
+}
+
+body,html{
+ padding:0;
+ margin:0;
+}
+
+body{
+ background:var(--1d2021);
+ color:var(--a89984);
+ font-size:16px;
+ box-sizing:border-box;
+ font-family:sans-serif;
+ margin:15px 7% 45px;
+ word-wrap:anywhere;
+ line-height:22px;
+ max-width:2000px;
+ position:relative;
+}
+
+.navigation{
+ position:absolute;
+ top:0;
+ right:0;
+ font-size:14px;
+ line-height:40px;
+}
+
+.navigation a{
+ color:var(--bdae93);
+ text-decoration:none;
+}
+
+.navigation a:hover{
+ text-decoration:underline;
+}
+
+.navigation a:not(:last-child)::after{
+ content:"|";
+ padding:0 7px;
+ display:inline-block;
+ color:var(--504945);
+}
+
+h1,h2,h3,h4,h5,h6{
+ padding:0;
+ margin:0 0 7px 0;
+ line-height:initial;
+ color:var(--bdae93);
+}
+
+h3,h4,h5,h6{
+ margin-bottom:14px;
+}
+
+/*
+ Web styles
+*/
+#overflow{
+ overflow:hidden;
+}
+
+/* Searchbox */
+.searchbox{
+ width:40%;
+ height:36px;
+ border:1px solid var(--504945);
+ background:var(--282828);
+ border-radius:2px;
+ margin-bottom:10px;
+ position:relative;
+}
+
+.searchbox .wrapper{
+ overflow:hidden;
+}
+
+.searchbox input[type="text"]{
+ width:100%;
+ padding-left:10px;
+}
+
+.searchbox input[type="text"]::placeholder{
+ color:var(--928374);
+}
+
+.searchbox input[type="submit"]{
+ float:right;
+ cursor:pointer;
+ padding:0 10px;
+}
+
+.searchbox input[type="submit"]:hover{
+ text-decoration:underline;
+}
+
+.searchbox input{
+ all:unset;
+ line-height:36px;
+ box-sizing:border-box;
+ height:36px;
+ color:var(--bdae93);
+}
+
+.searchbox:focus-within{
+ border:1px solid var(--928374);
+}
+
+.autocomplete{
+ display:none;
+ position:absolute;
+ top:35px;
+ left:-1px;
+ right:-1px;
+ background:var(--282828);
+ border:1px solid var(--928374);
+ border-top:none;
+ border-radius:0 0 2px 2px;
+ z-index:10;
+ overflow:hidden;
+}
+
+.autocomplete .entry{
+ overflow:hidden;
+ padding:4px 10px;
+ cursor:pointer;
+ outline:none;
+ user-select:none;
+}
+
+.autocomplete .entry:hover{
+ background:var(--3c3836);
+}
+
+.autocomplete .entry:focus{
+ background:var(--3c3836);
+}
+
+/* Tabs */
+.tabs, .filters{
+ overflow:hidden;
+ overflow-x:auto;
+ white-space:nowrap;
+}
+
+.tabs{
+ padding-bottom:10px;
+}
+
+.tabs .tab{
+ text-decoration:none;
+ color:var(--bdae93);
+ padding:4px 10px;
+ display:inline-block;
+}
+
+.tabs .tab:hover{
+ text-decoration:underline;
+}
+
+.tabs .tab.selected{
+ border-bottom:2px solid var(--bdae93);
+}
+
+/* Filters */
+.filters{
+ margin-bottom:7px;
+}
+
+.filters .filter{
+ display:inline-block;
+ margin-right:7px;
+ vertical-align:bottom;
+}
+
+.filters .filter .title{
+ font-size:13px;
+ margin:0 4px;
+}
+
+.filters .filter input,
+.filters .filter select{
+ all:unset;
+ user-select:none;
+ display:block;
+ border:1px solid var(--504945);
+ border-radius:2px;
+ font-size:14px;
+ padding:0 4px;
+ width:127px;
+ height:24px;
+}
+
+.timetaken{
+ font-size:14px;
+ font-weight:bold;
+ margin-bottom:10px;
+}
+
+
+/*
+ HOME
+*/
+.home{
+ min-height:100vh;
+ margin:0 auto;
+ display:table;
+ text-align:center;
+}
+
+.home .logo{
+ max-width:400px;
+ height:100px;
+ margin:0 auto 17px auto;
+}
+
+.home img{
+ line-height:100px;
+ font-size:60px;
+ text-align:center;
+ font-family:Times;
+ width:100%;
+ height:100%;
+ background:var(--282828);
+ display:block;
+ object-fit:contain;
+}
+
+.home #center{
+ display:table-cell;
+ vertical-align:middle;
+ width:500px;
+}
+
+.home .searchbox{
+ width:100%;
+ text-align:left;
+ margin-bottom:20px;
+}
+
+.home a{
+ color:inherit;
+}
+
+.home .subtext{
+ margin-top:17px;
+ line-height:16px;
+ font-size:12px;
+}
+
+
+/*
+ WEB
+*/
+
+/* Captcha */
+.captcha-wrapper{
+ position:relative;
+ max-width:400px;
+ margin:17px auto 0;
+ border:1px solid var(--928374);
+}
+
+.captcha{
+ padding-bottom:100%;
+ padding-top:6.2%;
+}
+
+.captcha-wrapper img{
+ position:absolute;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+}
+
+.captcha-controls{
+ position:absolute;
+ top:0;
+ left:0;
+ bottom:0;
+ right:0;
+ top:6.3%;
+}
+
+.captcha-wrapper img{
+ display:block;
+ background:#282828;
+}
+
+.captcha-wrapper input{
+ display:none;
+}
+
+.captcha-wrapper label{
+ float:left;
+ width:25%;
+ height:25%;
+ position:relative;
+ cursor:pointer;
+}
+
+.captcha-wrapper label:hover{
+ background:rgba(255,255,255,0.2);
+}
+
+.captcha-wrapper input:checked + label{
+ background:rgba(0,0,0,0.5);
+}
+
+.captcha-wrapper input:checked + label:after{
+ content:"";
+ position:absolute;
+ left:39%;
+ top:29%;
+ width:20%;
+ height:30%;
+ transform:rotate(45deg);
+ border-right:7px solid var(--ebdbb2);
+ border-bottom:7px solid var(--ebdbb2);
+ box-sizing:border-box;
+}
+
+.captcha-submit{
+ float:right;
+ margin:12px 0 4px;
+}
+
+.web .left{
+ width:40%;
+ float:left;
+}
+
+/* infobox */
+.infobox{
+ border:1px dashed var(--504945);
+ padding:10px;
+ margin-bottom:17px;
+ overflow:hidden;
+}
+
+.infobox .code{
+ white-space:initial;
+}
+
+.infobox ul{
+ padding-left:27px;
+ margin-bottom:0;
+}
+
+.infobox a{
+ color:var(--bdae93);
+}
+
+.infobox a:hover{
+ text-decoration:underline;
+}
+
+/* text-result */
+.web .text-result{
+ margin-bottom:30px;
+}
+
+.web .description{
+ white-space:pre-line;
+}
+
+.web .type{
+ border:1px solid var(--928374);
+ background:var(--282828);
+ padding:0 4px;
+ border-radius:2px;
+ font-size:14px;
+ line-height:16px;
+ float:left;
+ margin:2px 7px 0 0;
+}
+
+.web .url{
+ position:relative;
+}
+
+.web .url .part{
+ font-size:15px;
+ text-decoration:none;
+ color:var(--928374);
+}
+
+.web .separator::before{
+ content:"/";
+ padding:0 4px;
+ color:var(--504945);
+ font-size:12px;
+}
+
+.web .part:hover{
+ text-decoration:underline;
+}
+
+.web .hover{
+ display:block;
+ text-decoration:none;
+ color:var(--a89984);
+ overflow:hidden;
+ clear:left;
+ padding-top:7px;
+}
+
+.web .text-result .title{
+ font-size:18px;
+ color:var(--bdae93);
+ margin-bottom:7px;
+}
+
+.web .text-result a:visited .title{
+ color:var(--928374) !important;
+}
+
+.theme-white .web .text-result a:visited .title{
+ color:#7c6f64 !important;
+}
+
+.web .text-result .hover:hover .title{
+ text-decoration:underline;
+}
+
+.web .text-result .author{
+ font-style:italic;
+}
+
+.web .text-result .greentext{
+ font-size:14px;
+ color:var(--8ec07c);
+}
+
+.web .right-right .text-result:last-child,
+.web .right-left .text-result:last-child{
+ margin-bottom:0;
+}
+
+/* favicon */
+.favicon{
+ all:unset;
+ float:left;
+ cursor:pointer;
+}
+
+.favicon-dropdown{
+ display:none;
+ position:absolute;
+ top:25px;
+ background:var(--282828);
+ border:1px solid var(--504945);
+ border-radius:2px;
+ z-index:3;
+ word-wrap:normal;
+}
+
+.favicon-dropdown::before{
+ content:"";
+ position:absolute;
+ top:-10px;
+ left:2px;
+ border:5px solid transparent;
+ border-bottom:5px solid var(--504945);
+}
+
+.favicon-dropdown a{
+ text-decoration:none;
+ color:var(--bdae93);
+ display:block;
+ padding:2px 7px 2px 5px;
+ font-size:13px;
+}
+
+.favicon-dropdown a:hover{
+ text-decoration:underline;
+}
+
+.favicon-dropdown:hover,
+.favicon:focus + .favicon-dropdown,
+.favicon-dropdown:focus-within{
+ display:block;
+}
+
+.web .favicon img,
+.favicon-dropdown img{
+ margin:3px 7px 0 0;
+ width:16px;
+ height:16px;
+ font-size:12px;
+ line-height:16px;
+ text-align:center;
+ display:block;
+ text-align:left;
+ white-space:nowrap;
+}
+
+.favicon-dropdown img{
+ float:left;
+ margin:2px 7px 0 0;
+}
+
+/* thumbnails */
+.thumb-wrap{
+ position:relative;
+ float:right;
+ width:160px;
+ height:90px;
+ background:var(--282828);
+ border:1px solid var(--504945);
+ margin-left:7px;
+}
+
+.duration{
+ position:absolute;
+ right:0;
+ bottom:0;
+ padding:1px 2px;
+ line-height:14px;
+ background:var(--3c3836);
+ font-size:12px;
+ border-left:1px solid var(--504945);
+ border-top:1px solid var(--504945);
+ font-family:monospace;
+}
+
+.web .text-result:hover .thumb-wrap .duration{
+ display:none;
+}
+
+.thumb-wrap .thumb{
+ display:block;
+ object-fit:contain;
+ width:100%;
+ height:100%;
+}
+
+.thumb-wrap.portrait{
+ width:50px;
+}
+
+.thumb-wrap.square{
+ width:90px;
+}
+
+/* Next page */
+.nextpage{
+ margin:0 0 30px;
+ text-align:center;
+ display:block;
+ padding:10px;
+ border:1px solid var(--504945);
+ border-radius:2px;
+ text-decoration:none;
+ color:var(--bdae93);
+}
+
+.nextpage:hover{
+ text-decoration:underline;
+}
+
+/* Right wrapper */
+.web .right-wrapper{
+ width:60%;
+ float:right;
+ overflow:hidden;
+ padding-left:15px;
+ box-sizing:border-box;
+}
+
+.web .right-right,
+.web .right-left{
+ float:right;
+ width:50%;
+ padding:0 15px;
+ box-sizing:border-box;
+ overflow:hidden;
+ min-height:1px;
+}
+
+.web .right-right{
+ padding-right:0;
+}
+
+/*
+ Tables
+*/
+table{
+ width:100%;
+ text-align:left;
+ border-collapse:collapse;
+}
+
+table td{
+ width:50%;
+ padding:0;
+ vertical-align:top;
+}
+
+table tr td:first-child{
+ padding-right:7px;
+}
+
+table a{
+ display:block;
+ text-decoration:none;
+ color:var(--a89984);
+ padding:0 10px 0 0;
+}
+
+table tr a:last-child{
+ padding-right:0;
+}
+
+/* Related */
+.related{
+ margin-bottom:20px;
+}
+
+.related a{
+ padding-bottom:10px;
+ color:var(--bdae93);
+}
+
+.related a:hover{
+ text-decoration:underline;
+}
+
+/*
+ Answers
+*/
+.web .answer{
+ max-height:600px;
+ overflow:hidden;
+ padding-bottom:17px;
+ position:relative;
+}
+
+.web .answer::after{
+ content:"";
+ position:absolute;
+ bottom:0;
+ width:100%;
+ height:17px;
+ background:linear-gradient(transparent, var(--1d2021));
+ pointer-events:none;
+}
+
+.web .answer-title{
+ text-decoration:none;
+ color:var(--a89984);
+}
+
+.web .answer-title a:hover{
+ text-decoration:underline;
+}
+
+.web .spoiler:checked + .answer{
+ overflow:initial;
+ max-height:initial;
+}
+
+.web .spoiler{
+ display:none;
+}
+
+.web .spoiler-button{
+ display:block;
+ border:1px solid var(--504945);
+ border-radius:2px;
+ line-height:30px;
+ padding:0 7px;
+ text-align:center;
+ cursor:pointer;
+}
+
+.web .answer-wrapper{
+ margin-bottom:27px;
+}
+
+.web .spoiler-button:hover{
+ text-decoration:underline;
+}
+
+.web .spoiler-button::before{
+ content:"Show more";
+}
+
+.web .spoiler:checked + .answer + .spoiler-button::before{
+ content:"Show less";
+}
+
+/* Tables on left handside */
+.web .info-table{
+ margin:10px 0;
+ font-size:15px;
+ color:var(--928374);
+ background:var(--282828);
+ border:1px dashed var(--504945);
+}
+
+.web .info-table td{
+ padding:4px 10px;
+}
+
+.web .info-table tr td:first-child{
+ width:1%;
+ white-space:nowrap;
+ padding-right:17px;
+ color:var(--a89984);
+}
+
+.web .info-table tr:nth-child(even){
+ background:var(--1d2021);
+}
+
+.web .sublinks{
+ padding:17px 10px 0;
+ font-size:15px;
+ color:var(--#928374);
+}
+
+.web .sublinks table td{
+ padding-bottom:17px;
+}
+
+.web .sublinks table tr:last-child td:last-child{
+ padding-bottom:0;
+}
+
+.web .sublinks a:hover .title{
+ text-decoration:underline;
+}
+
+/* Wikipedia head */
+.web .wiki-head .photo{
+ float:right;
+ margin:0 1px 10px 10px;
+}
+.web .wiki-head .photo img{
+ display:block;
+ max-width:200px;
+ max-height:200px;
+ filter:drop-shadow(1px 0 0 var(--504945)) drop-shadow(-1px 0 0 var(--504945)) drop-shadow(0 -1px 0 var(--504945)) drop-shadow(0 1px 0 var(--504945));
+}
+
+.web .wiki-head .description{
+ clear:left;
+ padding-top:7px;
+ overflow:hidden;
+}
+
+.web .wiki-head table, .about table{
+ margin-top:17px;
+ border:1px dashed var(--504945);
+ background:var(--1d2021);
+}
+
+.web .wiki-head table td{
+ white-space:pre-line;
+}
+
+.web .wiki-head td, .about table td{
+ padding:4px 7px;
+ vertical-align:middle;
+}
+
+.web .wiki-head tr td:first-child, .about table tr td:first-child{
+ width:30%;
+ min-width:150px;
+}
+
+.web .wiki-head tr:nth-child(odd), .about table tr:nth-child(odd){
+ background:var(--282828);
+}
+
+.web .wiki-head .socials{
+ overflow:hidden;
+ margin-top:17px;
+}
+
+.web .wiki-head .socials a{
+ width:74px;
+ height:80px;
+ padding-right:4px;
+ float:left;
+ color:var(--bdae93);
+ text-decoration:none;
+ display:table;
+}
+
+.web .wiki-head .socials a:hover .title{
+ text-decoration:underline;
+}
+
+.web .wiki-head .socials .center{
+ display:table-cell;
+ vertical-align:middle;
+}
+
+.web .wiki-head .socials img{
+ margin:0 auto;
+ display:block;
+ text-align:center;
+ width:36px;
+ height:36px;
+ line-height:36px;
+}
+
+.web .wiki-head .socials .title{
+ margin-top:7px;
+ text-align:center;
+ font-size:13px;
+ line-height:13px;
+}
+
+.web .fullimg{
+ display:block;
+ max-width:100%;
+ max-height:150px;
+ margin:7px 0 17px;
+ box-sizing:border-box;
+ border:1px solid var(--504945);
+}
+
+/*
+ Code tags
+*/
+.code{
+ white-space:pre;
+ font-family:monospace;
+ background:var(--3c3836);
+ color:var(--bdae93);
+ padding:7px;
+ margin:4px 0 13px 0;
+ overflow-x:auto;
+ border-radius:2px;
+ border:1px solid var(--504945);
+}
+
+.code-inline{
+ display:inline;
+ font-family:monospace;
+ background:var(--282828);
+ color:var(--bdae93);
+ border:1px solid var(--928374);
+ padding:0 4px;
+ border-radius:2px;
+}
+
+/* Wiki-head titles and quotes */
+.web .wiki-head h2{
+ font-size:20px;
+ margin:20px 0 13px 0;
+}
+
+.web .wiki-head h2:first-child{
+ margin-top:10px;
+}
+
+.web .wiki-head a{
+ color:var(--bdae93);
+}
+
+.quote{
+ font-style:italic;
+ margin:10px 0 13px;
+ padding-left:10px;
+ border-left:1px solid #504945;
+}
+
+/*
+ Web images
+*/
+.web .images{
+ overflow:hidden;
+ margin:0 -5px;
+ font-size:0;
+}
+
+.web .images .duration{
+ display:none;
+ border:1px solid var(--504945);
+ right:5px;
+ bottom:5px;
+}
+
+.web .images .image:hover .duration{
+ display:block;
+}
+
+.web .images .image{
+ width:90px;
+ height:90px;
+ padding:5px;
+ position:relative;
+ line-height:90px;
+ display:inline-block;
+ text-align:center;
+ color:inherit;
+}
+
+.web .images .image img{
+ max-width:100%;
+ max-height:100%;
+ vertical-align:middle;
+}
+
+
+/*
+ Images tab
+*/
+
+#images{
+ overflow:hidden;
+ margin-bottom:10px;
+}
+
+#images .infobox{
+ width:40%;
+ box-sizing:border-box;
+}
+
+#images .image-wrapper{
+ line-height:15px;
+ width:20%;
+ float:left;
+}
+
+#images .image{
+ margin:0 auto;
+ width:250px;
+ max-width:100%;
+ padding:7px 7px 30px 7px;
+ box-sizing:border-box;
+ font-size:14px;
+}
+
+#images a{
+ color:inherit;
+ text-decoration:none;
+ display:block;
+}
+
+#images a:hover .title{
+ text-decoration:underline;
+}
+
+#images .thumb{
+ display:block;
+ height:180px;
+ margin-bottom:10px;
+ position:relative;
+}
+
+#images .duration{
+ display:block;
+ border:1px solid #504945;
+}
+
+#images .image:hover .duration{
+ display:none;
+}
+
+#images img{
+ width:100%;
+ height:100%;
+ object-fit:contain;
+}
+
+#images .image .title{
+ white-space:nowrap;
+ overflow:hidden;
+ margin-bottom:7px;
+ font-weight:bold;
+}
+
+#images .image .description{
+ overflow:hidden;
+ height:45px;
+}
+
+.nextpage.img{
+ width:50%;
+ margin:0 auto 50px;
+}
+
+#popup{
+ display:none;
+ position:fixed;
+ top:0;
+ left:0;
+ cursor:grab;
+ user-select:none;
+ pointer-events:none;
+ z-index:5;
+}
+
+#popup:active{
+ cursor:grabbing;
+}
+
+#popup-image{
+ border:1px solid var(--928374);
+ display:block;
+ margin:0 auto;
+ pointer-events:all;
+ width:100%;
+ height:100%;
+ object-fit:contain;
+ background:var(--282828);
+}
+
+#popup-status{
+ display:none;
+ position:fixed;
+ top:0;
+ left:0;
+ width:100%;
+ height:35px;
+ background:var(--1d2021);
+ border-bottom:1px solid var(--928374);
+ z-index:4;
+}
+
+#popup-bg{
+ background:var(--1d2021);
+ opacity:.5;
+ position:fixed;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+ display:none;
+ z-index:3;
+}
+
+#popup-status select{
+ display:block;
+ width:250px;
+}
+
+#popup-num,
+#popup-title{
+ display:table-cell;
+ width:0;
+ word-wrap:normal;
+ padding:0 10px;
+ line-height:35px;
+ color:var(--ebdbb2);
+ text-decoration:none;
+}
+
+#popup-title:hover{
+ text-decoration:underline;
+}
+
+#popup-title{
+ width:initial;
+ overflow:hidden;
+ height:35px;
+ display:block;
+}
+
+#popup-num{
+ font-weight:bold;
+}
+
+#popup-dropdown{
+ display:table-cell;
+ vertical-align:middle;
+ width:0;
+}
+
+/*
+ Settings page
+*/
+.web .settings{
+ margin-top:17px;
+ border:1px dashed var(--504945);
+ padding:7px 10px 0;
+}
+
+.web .setting{
+ margin-bottom:17px;
+}
+
+.web .setting .title{
+ font-size:14px;
+}
+
+.web .settings-submit{
+ margin:17px 10px;
+}
+
+.web .settings-submit input{
+ float:right;
+}
+
+.web .settings-submit a{
+ margin-right:17px;
+ color:var(--bdae93);
+}
+
+/*
+ About page
+*/
+.about a{
+ color:var(--bdae93);
+}
+
+.about h1, .about h2{
+ margin-top:17px;
+}
+
+.about table{
+ margin-bottom:17px;
+}
+
+.about table a{
+ display:inline;
+}
+
+
+/*
+ Syntax highlight
+*/
+.c-comment{
+ color:var(--comment);
+}
+.c-default{
+ color:var(--default);
+}
+.c-html{
+ color:var(--html);
+}
+.c-keyword{
+ color:var(--keyword);
+ font-weight:bold;
+}
+.c-string{
+ color:var(--string);
+}
+
+/*
+ Instances page
+*/
+.instances table{
+ white-space:nowrap;
+ margin-top:17px;
+}
+
+.instances a{
+ color:var(--bdae93);
+}
+
+.instances tbody tr:nth-child(even){
+ background:var(--282828);
+}
+
+.instances thead{
+ outline:1px solid var(--928374);
+ outline-offset:-1px;
+ background:var(--3c3836);
+ user-select:none;
+ z-index:2;
+ position:sticky;
+ top:0;
+}
+
+.instances th{
+ cursor:row-resize;
+}
+
+.instances th:hover{
+ background:var(--504945);
+}
+
+.instances tbody{
+ outline:1px solid var(--504945);
+ outline-offset:-1px;
+ position:relative;
+ top:-1px;
+}
+
+.instances tbody tr:hover{
+ background:var(--3c3836);
+ cursor:pointer;
+}
+
+.instances .arrow{
+ display:inline-block;
+ position:relative;
+ top:6px;
+ margin-right:7px;
+ width:0;
+ height:0;
+ border:6px solid transparent;
+ border-top:10px solid var(--bdae93);
+}
+
+.instances .arrow.up{
+ top:0;
+ border:6px solid transparent;
+ border-bottom:10px solid var(--bdae93);
+}
+
+.instances th, .instances td{
+ padding:4px 7px;
+ width:0;
+}
+
+.instances .extend{
+ width:unset;
+ overflow:hidden;
+ max-width:200px;
+}
+
+.instances .popup-wrapper{
+ display:none;
+ position:fixed;
+ left:50%;
+ top:50%;
+ transform:translate(-50%, -50%);
+ width:800px;
+ max-width:100%;
+ max-height:100%;
+ overflow-x:auto;
+ padding:17px;
+ box-sizing:border-box;
+ pointer-events:none;
+ z-index:3;
+}
+
+.instances .popup{
+ border:1px solid var(--928374);
+ background:var(--282828);
+ padding:7px 10px;
+ pointer-events:initial;
+}
+
+.instances ul{
+ padding-left:20px;
+}
+
+.instances .go-back{
+ margin-top:17px;
+ display:inline-block;
+}
+
+
+/*
+ Responsive image
+*/
+@media only screen and (max-width: 1454px){ #images .image-wrapper{ width:25%; } }
+@media only screen and (max-width: 1161px){ #images .image-wrapper{ width:33.3333%; } }
+@media only screen and (max-width: 750px){ #images .image-wrapper{ width:50%; } }
+@media only screen and (max-width: 450px){ #images .image-wrapper{ width:100%; } }
+
+
+/*
+ Responsive design
+*/
+@media only screen and (max-width: 1550px){
+
+ .web .right-right,
+ .web .right-left{
+ float:none;
+ width:initial;
+ padding:0 0 0 15px;
+ }
+
+ .web .left,
+ .searchbox,
+ #images .infobox{
+ width:60%;
+ }
+
+ .web .right-wrapper{
+ width:40%;
+ }
+}
+
+@media only screen and (max-width: 1000px){
+ form{
+ padding-top:27px;
+ }
+
+ .navigation{
+ left:0;
+ right:unset;
+ line-height:22px;
+ }
+
+ .nextpage.img{
+ width:initial;
+ }
+
+ .web .right-right,
+ .web .right-left{
+ border:none;
+ padding:0;
+ }
+
+ .web .right-wrapper{
+ float:none;
+ padding:0;
+ width:initial;
+ }
+
+ .web .left,
+ .searchbox{
+ width:100%;
+ }
+
+ body:not(.instances) table td{
+ display:block;
+ width:100%;
+ }
+
+ table a{
+ padding:0;
+ }
+
+ .web.has-answer .left::before{
+ display:block;
+ content:"Results";
+ font-size:24px;
+ font-weight:bold;
+ margin-bottom:17px;
+ color:var(--bdae93);
+ }
+
+ .web .answer{
+ max-height:200px;
+ }
+
+ .web .wiki-head tr td:first-child,
+ .web .info-table tr td:first-child{
+ text-decoration:underline;
+ }
+
+ #images .infobox{
+ width:100%;
+ }
+}
diff --git a/static/themes/Catppuccin Latte.css b/static/themes/Catppuccin Latte.css
new file mode 100644
index 0000000..7c70549
--- /dev/null
+++ b/static/themes/Catppuccin Latte.css
@@ -0,0 +1,20 @@
+:root{
+ /* background */
+ --1d2021: #eff1f5;
+ --282828: #eff1f5;
+ --3c3836: #dce0e8;
+ --504945: #5c5f77;
+
+ /* font */
+ --928374: #8c8fa1;
+ --a89984: #4c4f69;
+ --bdae93: #4c4f69;
+ --8ec07c: #df8e1d;
+ --ebdbb2: #4c4f69;
+
+ /* code highlighter */
+ --comment: #e64553;
+ --default: #eff1f5;
+ --keyword: #df8e1d;
+ --string: #209fb5;
+}
diff --git a/static/themes/Catppuccin Mocha.css b/static/themes/Catppuccin Mocha.css
new file mode 100644
index 0000000..03a3fa8
--- /dev/null
+++ b/static/themes/Catppuccin Mocha.css
@@ -0,0 +1,20 @@
+:root{
+ /* background */
+ --1d2021: #1e1e2e;
+ --282828: #313244;
+ --3c3836: #45475a;
+ --504945: #585b70;
+
+ /* font */
+ --928374: #bac2de;
+ --a89984: #a6adc8;
+ --bdae93: #cdd6f4;
+ --8ec07c: #a6e3a1;
+ --ebdbb2: #f9e2af;
+
+ /* code highlighter */
+ --comment: #f5e0dc;
+ --default: #f2cdcd;
+ --keyword: #fab387;
+ --string: #74c7ec;
+}
diff --git a/static/themes/Cream.css b/static/themes/Cream.css
new file mode 100644
index 0000000..3d6b615
--- /dev/null
+++ b/static/themes/Cream.css
@@ -0,0 +1,31 @@
+:root{
+ /* background */
+ --1d2021: #bdae93;
+ --282828: #a89984;
+ --3c3836: #a89984;
+ --504945: #504945;
+
+ /* font */
+ --928374: #1d2021;
+ --a89984: #282828;
+ --bdae93: #3c3836;
+ --8ec07c: #52520e;
+ --ebdbb2: #1d2021;
+
+ /* code highlighter */
+ --comment: #6a4400;
+ --default: #d4be98;
+ --keyword: #4a4706;
+ --string: #076678;
+
+ /* color codes for instance list */
+ --green: #636311;
+ --yellow: #8a6214;
+ --red: #711410;
+}
+
+.autocomplete .entry:hover,
+.instances th:hover
+{
+ background:#928374;
+}
diff --git a/static/themes/Dark Christmas.css b/static/themes/Dark Christmas.css
new file mode 100644
index 0000000..0a097f1
--- /dev/null
+++ b/static/themes/Dark Christmas.css
@@ -0,0 +1,95 @@
+body{
+ background-image:url("/static/misc/christmas-dark-bg.png");
+}
+
+.home::before{
+ content:"";
+ position:fixed;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+ background-image:url("/static/misc/snow.png");
+ pointer-events:none;
+ z-index:-1;
+ animation:snowfall 12s linear infinite;
+ opacity:.6;
+}
+
+.home{
+ background:#1d2021;
+}
+
+@keyframes snowfall{
+ from{
+ background-position:0% 0px;
+ }
+
+ to{
+ background-position:0% 600px;
+ }
+}
+
+.web .answer::after{
+ display:none;
+}
+
+:root{
+ /* background */
+ --1d2021: #1d2021;
+ --282828: #282828;
+ --3c3836: #3c3836;
+ --504945: #504945;
+
+ /* font */
+ --928374: #928374;
+ --a89984: #bdae93;
+ --bdae93: #d5c4a1;
+ --8ec07c: #8ec07c;
+ --ebdbb2: #ebdbb2;
+
+ /* code highlighter */
+ --comment: #9e8e73;
+ --default: #d4be98;
+ --keyword: #d8a657;
+ --string: #7daea7;
+
+ /* color codes for instance list */
+ --green: #b8bb26;
+ --yellow: #d8a657;
+ --red: #fb4934;
+}
+
+.web .wiki-head table, .about table, .web .info-table, .instances table{
+ background:#1d202170;
+ color:#bdae93;
+}
+
+.web .wiki-head tr:nth-child(odd), .about table tr:nth-child(odd), .web .info-table tr:nth-child(even), .nextpage, .spoiler-button, .instances tbody tr:nth-child(even){
+ background:#28282870;
+ color:#bdae93;
+}
+
+.instances tbody tr:hover{
+ background:#3c383690;
+}
+
+.wiki-head .description{
+ overflow:initial !important;
+}
+
+.wiki-head .photo{
+ position:relative;
+}
+
+.wiki-head .photo::after{
+ content:"";
+ position:absolute;
+ width:100px;
+ height:100px;
+ background-image:url("/static/misc/christmas-hat.png");
+ background-size:contain;
+ top:-61px;
+ left:-31px;
+ transform:rotate(310deg);
+}
diff --git a/static/themes/Gore's shitty theme.css b/static/themes/Gore's shitty theme.css
new file mode 100644
index 0000000..d32f44a
--- /dev/null
+++ b/static/themes/Gore's shitty theme.css
@@ -0,0 +1,322 @@
+:root{
+ --1d2021:#1d2021;
+ --282828:#282828;
+ --3c3836:#3c3836;
+ --504945:#504945;
+
+ /* font */
+ --928374:#928374;
+ --a89984:#c9c5bf;
+ --bdae93:#bdae93;
+ --8ec07c:#8ec07c;
+ --ebdbb2:#ebdbb2;
+}
+
+body{
+ padding:15px 4% 40px;
+ margin:unset;
+}
+
+h1, h2, h3, h4, h5, h6{
+ padding:0;
+ margin:0 0 7px 0;
+ line-height:initial;
+ color:var(--bdae93);
+}
+
+h3, h4, h5, h6{
+ margin-bottom:14px;
+}
+
+/*
+ Web styles
+*/
+
+.searchbox input[type="submit"]{
+ float:right;
+ cursor:pointer;
+ padding:0 10px;
+ border-left:1px solid var(--504945);
+ background:#723c0b;
+}
+
+.searchbox input{
+ all:unset;
+ line-height:36px;
+ box-sizing:border-box;
+ height:36px;
+}
+
+.searchbox:focus-within{
+ border:1px solid #ee8a9c;
+}
+
+.autocomplete{
+ display:none;
+ position:absolute;
+ top:35px;
+ left:-1px;
+ right:-1px;
+ background:var(--282828);
+ border:1px solid var(--504945);
+ border-top:none;
+ border-radius:0 0 2px 2px;
+ z-index:10;
+}
+
+.autocomplete .entry{
+ overflow:hidden;
+ padding:4px 10px;
+ cursor:pointer;
+}
+
+.autocomplete .title{
+ float:left;
+}
+
+.autocomplete .subtext{
+ float:right;
+ font-size:14px;
+ color:var(--928374);
+ margin-left:7px;
+}
+
+/* Tabs */
+
+.tabs{
+ padding-bottom:0px;
+}
+
+.tabs .tab{
+ text-decoration:none;
+ color:#d3d0c1;
+ padding:0px 10px;
+ display:inline-block;
+}
+
+.tabs .tab.selected{
+ border-bottom:2px solid #fc92a5;
+}
+
+/* Filters */
+.filters{
+ padding-bottom:12px;
+ padding-top:7px;
+ margin-bottom:7px;
+ background-color:#232525;
+}
+
+.filters .filter{
+ display:inline-block;
+ margin-right:7px;
+}
+
+.filters .filter .title{
+ font-size:13px;
+}
+
+.filters .filter input,
+.filters .filter select{
+ all:unset;
+ display:block;
+ border:1px solid var(--504945);
+ border-radius:2px;
+ font-size:14px;
+ padding:0 2px;
+ width:127px;
+ height:22px;
+}
+
+/* infobox */
+.web .infobox{
+ border:1px dashed var(--504945);
+ padding:10px;
+ margin-bottom:17px;
+}
+
+.web .infobox .code{
+ white-space:initial;
+}
+
+.web .infobox ul{
+ padding-left:27px;
+ margin-bottom:0;
+}
+
+.web .infobox a{
+ color:var(--bdae93);
+}
+
+.web .infobox a:hover{
+ text-decoration:underline;
+}
+
+/* text-result */
+
+.web .url .part{
+ font-size:15px;
+ text-decoration:none;
+ color:#90c186;
+}
+
+.web .separator::before{
+ content:"/";
+ padding:0 4px;
+ color:#5ab442;
+ font-size:12px;
+}
+
+.web .hover{
+ display:block;
+ text-decoration:none;
+ color:var(--a89984);
+ overflow:hidden;
+ clear:left;
+ padding-top:3px;
+}
+
+.web .text-result .title{
+ font-size:18px;
+ color:#81b5f4;
+ margin-bottom:5px;
+}
+
+.web .text-result a:visited .title{
+ color:#aa77c1 !important;
+}
+
+.theme-white .web .text-result a:visited .title{
+ color:#9760b1 !important;
+}
+
+.web .text-result .greentext{
+ font-size:14px;
+ color:var(--bdae93);
+}
+
+/* favicon */
+
+.favicon-dropdown a{
+ text-decoration:none;
+ color:#d3d0c1;
+ display:block;
+ padding:2px 7px 2px 5px;
+ font-size:13px;
+}
+
+.web .favicon img, .favicon-dropdown img{
+ margin:3px 7px 0 0;
+ height:16px;
+ font-size:12px;
+ line-height:16px;
+ display:block;
+ text-align:left;
+}
+
+.web .sublinks{
+ padding:17px 10px;
+ font-size:15px;
+ color:var(--#928374);
+}
+
+.web .text-result .sublinks:last-child{
+ padding-bottom:0;
+}
+
+/* Wikipedia head */
+.wiki-head{
+ padding:5px;
+ background-color:#322f2b;
+}
+
+/*
+ Images tab
+*/
+
+#images{
+ line-height:15px;
+ overflow:hidden;
+ margin-bottom:10px;
+}
+
+#images .image-wrapper{
+ width:20%;
+ float:left;
+}
+
+#images .image .title{
+ white-space:nowrap;
+ overflow:hidden;
+ margin-bottom:7px;
+ font-weight:bold;
+ color:var(--bdae93);
+}
+
+#popup-status{
+ display:none;
+ position:fixed;
+ top:0;
+ left:0;
+ width:100%;
+ height:35px;
+ background:var(--1d2021);
+ border-bottom:1px solid var(--928374);
+}
+
+/*
+ Settings page
+*/
+
+.web .settings-submit a{
+ margin-right:17px;
+ color:#bdae93;
+}
+
+/*
+ Responsive image
+*/
+@media only screen and (max-width:1454px){
+ #images .image-wrapper{
+ width:25%;
+ }
+}
+
+@media only screen and (max-width:1161px){
+ #images .image-wrapper{
+ width:25%;
+ }
+}
+
+@media only screen and (max-width:750px){
+ #images .image-wrapper{
+ width:50%;
+ }
+}
+
+@media only screen and (max-width:450px){
+ #images .image-wrapper{
+ width:100%;
+ }
+}
+
+/*
+ Responsive design
+*/
+
+@media only screen and (max-width:1550px){
+.web .left,
+ .searchbox{
+ width:60%;
+ }
+}
+
+@media only screen and (max-width:1100px){
+.web .left,
+ .searchbox{
+ width:100%;
+ }
+}
+
+.type{
+ color:var(--bdae93);
+}
diff --git a/static/themes/Kuuro.css b/static/themes/Kuuro.css
new file mode 100644
index 0000000..fdb60e5
--- /dev/null
+++ b/static/themes/Kuuro.css
@@ -0,0 +1,17 @@
+:root{
+ --1d2021: #101010;
+ --282828: #1a1a1a;
+ --3c3836: #1e1e1e;
+ --504945: #232323;
+
+ --928374: #949494;
+ --a89984: #d2d2d2;
+ --bdae93: #d2d2d2;
+ --8ec07c: #99c794;
+ --ebdbb2: #d2d2d2;
+
+ --comment: #5f6364;
+ --default: #cccece;
+ --keyword: #c594c5;
+ --string: #99c794;
+} \ No newline at end of file
diff --git a/static/themes/White Christmas.css b/static/themes/White Christmas.css
new file mode 100644
index 0000000..5368596
--- /dev/null
+++ b/static/themes/White Christmas.css
@@ -0,0 +1,94 @@
+body{
+ background:#e7e7e7;
+ background-image:url("/static/misc/christmas-white-bg.png");
+}
+
+.home::before{
+ content:"";
+ position:fixed;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+ background:#b9b5b380;
+ background-image:url("/static/misc/snow.png");
+ pointer-events:none;
+ z-index:-1;
+ animation:snowfall 12s linear infinite;
+}
+
+.web .answer::after{
+ display:none;
+}
+
+@keyframes snowfall{
+ from{
+ background-position:0% 0px;
+ }
+
+ to{
+ background-position:0% 600px;
+ }
+}
+
+:root{
+ /* background */
+ --1d2021: #bdae93;
+ --282828: #e7e7e7;
+ --3c3836: #e7e7e7;
+ --504945: #504945;
+
+ /* font */
+ --928374: #1d2021;
+ --a89984: #282828;
+ --bdae93: #3c3836;
+ --8ec07c: #52520e;
+ --ebdbb2: #1d2021;
+
+ /* code highlighter */
+ --comment: #6a4400;
+ --default: #d4be98;
+ --keyword: #4a4706;
+ --string: #076678;
+
+ /* color codes for instance list */
+ --green: #636311;
+ --yellow: #8a6214;
+ --red: #711410;
+}
+
+.autocomplete .entry:hover{
+ background:#92837480;
+}
+
+.web .wiki-head table, .about table, .web .info-table, .instances table{
+ background:#a8998470;
+}
+
+.web .wiki-head tr:nth-child(odd), .about table tr:nth-child(odd), .web .info-table tr:nth-child(even), .nextpage, .spoiler-button, .instances tbody tr:nth-child(even){
+ background:#bdae9370;
+}
+
+.instances tbody tr:hover{
+ background:#92837480;
+}
+
+.wiki-head .description{
+ overflow:initial !important;
+}
+
+.wiki-head .photo{
+ position:relative;
+}
+
+.wiki-head .photo::after{
+ content:"";
+ position:absolute;
+ width:100px;
+ height:100px;
+ background-image:url("/static/misc/christmas-hat.png");
+ background-size:contain;
+ top:-61px;
+ left:-31px;
+ transform:rotate(310deg);
+}
diff --git a/static/themes/Wine.css b/static/themes/Wine.css
new file mode 100644
index 0000000..2d79f02
--- /dev/null
+++ b/static/themes/Wine.css
@@ -0,0 +1,40 @@
+:root
+{
+ --accent : #f79e98;
+ --1d2021 : #180d0c;
+ --282828 : #180d0c;
+ --3c3836 : #251615;
+ --504945 : #251615;
+ --928374 : var(--accent);
+ --a89984 : #d8c5c4;
+ --bdae93 : #d8c5c4;
+ --8ec07c : var(--accent);
+ --ebdbb2 : #d8c5c4;
+ --comment: #928374;
+ --default: #DCC9BC;
+ --keyword: #F07342;
+ --string : var(--accent);
+ --green : #959A6B;
+ --yellow : #E39C45;
+ --red : #CF223E;
+ --white : var(--a89984);
+ --black : var(--1d2021);
+ --hover : #b18884
+}
+
+ a.link, a { color: var(--accent); text-decoration: none; }
+ .searchbox { width: 23%; }
+ .filters filter select { color: #E39C45; }
+ .web .separator::before { color: var(--white) }
+ .searchbox input[type="text"]::placeholder { color: var(--white); }
+ a.link:hover
+ {
+ color: var(--hover);
+ text-shadow: 0 0 .2rem var(--hover);
+ }
+ .code-inline
+ { border-color: var(--default); font-family: monospace;}
+ .home #center a
+ { color: var(--accent); }
+ .home .subtext
+ { color: var(--white); }
diff --git a/static/themes/gentoo.css b/static/themes/gentoo.css
new file mode 100644
index 0000000..b727108
--- /dev/null
+++ b/static/themes/gentoo.css
@@ -0,0 +1,20 @@
+:root{
+ /* background */
+ --1d2021: #21222d;
+ --282828: #393657;
+ --3c3836: #27273c;
+ --504945: #aa85e1;
+
+ /* font */
+ --928374: #906be3;
+ --a89984: #9794ac;
+ --bdae93: #cec0f2;
+ --8ec07c: #5db6e1;
+ --ebdbb2: #b194f5;
+
+ /* code highlighter */
+ --comment: #a0c0a4;
+ --default: #f00;
+ --keyword: #9376e4;
+ --string: #ecd78f;
+}