about summary refs log tree commit diff stats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitignore1
-rw-r--r--.prettierignore3
-rw-r--r--.prettierrc7
-rw-r--r--README.md9
-rw-r--r--package.json83
-rw-r--r--rollup.config.js35
-rw-r--r--src/assets/javascripts/localise.js50
-rw-r--r--src/assets/javascripts/services.js1641
-rw-r--r--src/assets/javascripts/utils.js197
-rw-r--r--src/config.json2158
-rw-r--r--src/manifest.json171
-rw-r--r--src/pages/background/background.html10
-rw-r--r--src/pages/background/background.js576
-rw-r--r--src/pages/messages/no_instance.html36
-rw-r--r--src/pages/options/index.js390
-rw-r--r--src/pages/options/index.pug10
-rw-r--r--src/pages/options/init.js66
-rw-r--r--src/pages/options/widgets/general.js216
-rw-r--r--src/pages/options/widgets/general.pug88
-rw-r--r--src/pages/options/widgets/services.pug83
-rw-r--r--src/pages/popup/popup.js192
-rw-r--r--src/pages/popup/style.css48
-rw-r--r--src/pages/src/App.svelte118
-rw-r--r--src/pages/src/General/Exceptions.svelte108
-rw-r--r--src/pages/src/General/General.svelte70
-rw-r--r--src/pages/src/General/SettingsButtons.svelte118
-rw-r--r--src/pages/src/Services/Instances.svelte234
-rw-r--r--src/pages/src/Services/RedirectType.svelte84
-rw-r--r--src/pages/src/Services/Services.svelte119
-rw-r--r--src/pages/src/Sidebar.svelte43
-rw-r--r--src/pages/src/components/Button.svelte29
-rw-r--r--src/pages/src/components/Checkbox.svelte52
-rw-r--r--src/pages/src/components/Input.svelte41
-rw-r--r--src/pages/src/components/Label.svelte18
-rw-r--r--src/pages/src/components/Row.svelte12
-rw-r--r--src/pages/src/components/RowCheckbox.svelte14
-rw-r--r--src/pages/src/components/RowSelect.svelte19
-rw-r--r--src/pages/src/components/Select.svelte34
-rw-r--r--src/pages/src/icons/AboutIcon.svelte11
-rw-r--r--src/pages/src/icons/AddIcon.svelte3
-rw-r--r--src/pages/src/icons/CloseIcon.svelte3
-rw-r--r--src/pages/src/icons/ExportIcon.svelte5
-rw-r--r--src/pages/src/icons/GeneralIcon.svelte13
-rw-r--r--src/pages/src/icons/ImportIcon.svelte5
-rw-r--r--src/pages/src/icons/PingIcon.svelte5
-rw-r--r--src/pages/src/icons/ResetIcon.svelte15
-rw-r--r--src/pages/src/icons/ServicesIcon.svelte11
-rw-r--r--src/pages/src/main.js7
-rw-r--r--src/pages/src/stores.js5
-rw-r--r--src/pages/stylesheets/styles.css463
-rw-r--r--src/pages/widgets/head.pug7
-rw-r--r--src/pages/widgets/links.pug22
-rw-r--r--test-conditions.md22
53 files changed, 4083 insertions, 3697 deletions
diff --git a/.gitignore b/.gitignore
index b1b53216..7ca34ebb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ package-lock.json
 src/pages/options/index.html
 src/pages/popup/popup.html
 pnpm-lock.yaml
+src/pages/options/build
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..665abdb7
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,3 @@
+src/_locales/
+.github/
+.gitea/
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..2f1b5f52
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+  "printWidth": 120,
+  "semi": false,
+  "singleQuote": false,
+  "trailingComma": "es5",
+  "arrowParens": "avoid"
+}
diff --git a/README.md b/README.md
index 6d0ad08b..c71984a1 100644
--- a/README.md
+++ b/README.md
@@ -11,34 +11,42 @@ A browser extension that redirects YouTube, Twitter, TikTok... requests to alter
 </a>
 
 ## Translate
+
 <a href="https://hosted.weblate.org/projects/libredirect/extension">
     <img src ="./img/weblate.svg">
 </a>
 
 ## Development
+
 Install [Node.js](https://nodejs.org/)
+
 ```bash
 git clone https://github.com/libredirect/browser_extension
 cd browser_extension
 npm install
 npm run html # Generates html using Pug
 ```
+
 #### Run on Firefox
+
 ```bash
 npm run start
 ```
 
 #### Build a zip package for Firefox
+
 ```bash
 npm run build
 ```
 
 #### Install the zip package on Firefox (temporarily)
+
 1. Type in the address bar: `about:debugging#/runtime/this-firefox`
 2. Press `Load Temporary Add-on...`
 3. Select `libredirect-VERSION.zip` from `web-ext-artifacts` folder
 
 #### Install the zip package on Firefox ESR, Developer Edition, Nightly
+
 1. Type in the address bar: `about:config`
 2. Set `xpinstall.signatures.required` to `false`
 3. Type in the address bar: `about:addons`
@@ -46,6 +54,7 @@ npm run build
 5. Select `libredirect-VERSION.zip` from `web-ext-artifacts` folder
 
 #### Run on Chromium
+
 1. Open `chrome://extensions`
 2. Enable `dev mode`
 3. Select `load unpacked extension`
diff --git a/package.json b/package.json
index 79147f4d..234dd5e1 100644
--- a/package.json
+++ b/package.json
@@ -1,39 +1,48 @@
 {
-	"name": "libredirect",
-	"description": "Redirects YouTube, Twitter, TikTok and more to privacy friendly frontends.",
-	"engines": {
-		"node": ">=16.13.1",
-		"npm": ">=8.1.2"
-	},
-	"scripts": {
-		"start": "web-ext run",
-		"nightly": "web-ext run --firefox=/home/esmail/software/firefox_nightly/firefox",
-		"android": "web-ext run -t firefox-android --adb-device emulator-5554 --firefox-apk org.mozilla.fenix",
-		"build": "web-ext build",
-		"test": "web-ext lint",
-		"html": "pug --basedir ./ --obj ./src/config.json src/pages/options/index.pug --out src/pages/options/ && pug --basedir ./ --obj ./src/config.json src/pages/popup/popup.pug --out src/pages/popup/"
-	},
-	"repository": {
-		"type": "git",
-		"url": "git+https://github.com/libredirect/libredirect.git"
-	},
-	"author": "LibRedirect",
-	"license": "GPL-3.0-only",
-	"bugs": {
-		"url": "https://github.com/libredirect/libredirect/issues"
-	},
-	"homepage": "https://libredirect.github.io",
-	"devDependencies": {
-		"pug-cli": "^1.0.0-alpha6",
-		"web-ext": "^7.2.0"
-	},
-	"webExt": {
-		"sourceDir": "./src/",
-		"run": {
-			"browserConsole": true
-		},
-		"build": {
-			"overwriteDest": true
-		}
-	}
+  "name": "libredirect",
+  "type": "module",
+  "description": "Redirects YouTube, Twitter, TikTok and more to privacy friendly frontends.",
+  "engines": {
+    "node": ">=16.13.1",
+    "npm": ">=8.1.2"
+  },
+  "scripts": {
+    "start": "web-ext run",
+    "nightly": "web-ext run --firefox=/home/esmail/software/firefox_nightly/firefox",
+    "android": "web-ext run -t firefox-android --adb-device emulator-5554 --firefox-apk org.mozilla.fenix",
+    "build": "web-ext build",
+    "test": "web-ext lint",
+    "html": "rollup -c"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/libredirect/libredirect.git"
+  },
+  "author": "LibRedirect",
+  "license": "GPL-3.0-only",
+  "bugs": {
+    "url": "https://github.com/libredirect/libredirect/issues"
+  },
+  "homepage": "https://libredirect.github.io",
+  "devDependencies": {
+    "prettier": "3.3.3",
+    "pug-cli": "^1.0.0-alpha6",
+    "web-ext": "^7.2.0",
+    "@rollup/plugin-commonjs": "^24.0.0",
+    "@rollup/plugin-node-resolve": "^15.0.0",
+    "@rollup/plugin-terser": "^0.4.0",
+    "rollup": "^3.15.0",
+    "rollup-plugin-css-only": "^4.3.0",
+    "rollup-plugin-svelte": "^7.1.2",
+    "svelte": "^3.55.0"
+  },
+  "webExt": {
+    "sourceDir": "./src/",
+    "run": {
+      "browserConsole": true
+    },
+    "build": {
+      "overwriteDest": true
+    }
+  }
 }
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 00000000..71222de8
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,35 @@
+import svelte from "rollup-plugin-svelte"
+import commonjs from "@rollup/plugin-commonjs"
+import terser from "@rollup/plugin-terser"
+import resolve from "@rollup/plugin-node-resolve"
+import css from "rollup-plugin-css-only"
+
+const production = !process.env.ROLLUP_WATCH
+
+export default {
+  input: "src/pages/src/main.js",
+  output: {
+    sourcemap: true,
+    format: "iife",
+    name: "app",
+    file: "src/pages/options/build/bundle.js",
+  },
+  plugins: [
+    svelte({
+      compilerOptions: {
+        dev: !production,
+      },
+    }),
+    css({ output: "bundle.css" }),
+    resolve({
+      browser: true,
+      dedupe: ["svelte"],
+      exportConditions: ["svelte"],
+    }),
+    commonjs(),
+    // production && terser(),
+  ],
+  watch: {
+    clearScreen: false,
+  },
+}
diff --git a/src/assets/javascripts/localise.js b/src/assets/javascripts/localise.js
index c0936873..d26d07d4 100644
--- a/src/assets/javascripts/localise.js
+++ b/src/assets/javascripts/localise.js
@@ -1,34 +1,34 @@
 window.browser = window.browser || window.chrome
 
 function localisePage() {
-	/**
-	 * @param {string} tag
-	 */
-	function getMessage(tag) {
-		return tag.replace(/__MSG_(\w+)__/g, (_match, v1) => {
-			return v1 ? browser.i18n.getMessage(v1) : null
-		})
-	}
+  /**
+   * @param {string} tag
+   */
+  function getMessage(tag) {
+    return tag.replace(/__MSG_(\w+)__/g, (_match, v1) => {
+      return v1 ? browser.i18n.getMessage(v1) : null
+    })
+  }
 
-	const elements = document.querySelectorAll("[data-localise]")
-	for (let i in elements)
-		if (elements.hasOwnProperty(i)) {
-			const obj = elements[i]
-			const tag = obj.getAttribute("data-localise").toString()
-			const msg = getMessage(tag)
-			if (msg && msg !== tag) obj.textContent = msg
-		}
+  const elements = document.querySelectorAll("[data-localise]")
+  for (let i in elements)
+    if (elements.hasOwnProperty(i)) {
+      const obj = elements[i]
+      const tag = obj.getAttribute("data-localise").toString()
+      const msg = getMessage(tag)
+      if (msg && msg !== tag) obj.textContent = msg
+    }
 
-	const placeholders = document.querySelectorAll("[data-localise-placeholder]")
-	for (let i in placeholders)
-		if (placeholders.hasOwnProperty(i)) {
-			const obj = placeholders[i]
-			const tag = obj.getAttribute("data-localise-placeholder").toString()
-			const msg = getMessage(tag)
-			if (msg && msg !== tag) obj.placeholder = msg
-		}
+  const placeholders = document.querySelectorAll("[data-localise-placeholder]")
+  for (let i in placeholders)
+    if (placeholders.hasOwnProperty(i)) {
+      const obj = placeholders[i]
+      const tag = obj.getAttribute("data-localise-placeholder").toString()
+      const msg = getMessage(tag)
+      if (msg && msg !== tag) obj.placeholder = msg
+    }
 }
 
 export default {
-	localisePage,
+  localisePage,
 }
diff --git a/src/assets/javascripts/services.js b/src/assets/javascripts/services.js
index b9ee64e5..39347845 100644
--- a/src/assets/javascripts/services.js
+++ b/src/assets/javascripts/services.js
@@ -6,25 +6,25 @@ window.browser = window.browser || window.chrome
 let config, options
 
 async function init() {
-	options = await utils.getOptions()
-	config = await utils.getConfig()
+  options = await utils.getOptions()
+  config = await utils.getConfig()
 }
 
 init()
 browser.storage.onChanged.addListener(init)
 
 function all(service, frontend, options, config) {
-	let instances = []
-	if (!frontend) {
-		for (const frontend in config.services[service].frontends) {
-			if (options[frontend]) {
-				instances.push(...options[frontend])
-			}
-		}
-	} else if (options[frontend]) {
-		instances = options[frontend]
-	}
-	return instances
+  let instances = []
+  if (!frontend) {
+    for (const frontend in config.services[service].frontends) {
+      if (options[frontend]) {
+        instances.push(...options[frontend])
+      }
+    }
+  } else if (options[frontend]) {
+    instances = options[frontend]
+  }
+  return instances
 }
 
 /**
@@ -34,17 +34,17 @@ function all(service, frontend, options, config) {
  * @param {string} frontend
  */
 function regexArray(service, url, config, frontend) {
-	let targetList = config.services[service].targets
-	if (frontend && 'excludeTargets' in config.services[service].frontends[frontend]) {
-		targetList = targetList.filter(val =>
-			!config.services[service].frontends[frontend].excludeTargets.includes(targetList.indexOf(val))
-		)
-	}
-	for (const targetString in targetList) {
-		const target = new RegExp(targetList[targetString])
-		if (target.test(url.href)) return true
-	}
-	return false
+  let targetList = config.services[service].targets
+  if (frontend && "excludeTargets" in config.services[service].frontends[frontend]) {
+    targetList = targetList.filter(
+      val => !config.services[service].frontends[frontend].excludeTargets.includes(targetList.indexOf(val))
+    )
+  }
+  for (const targetString in targetList) {
+    const target = new RegExp(targetList[targetString])
+    if (target.test(url.href)) return true
+  }
+  return false
 }
 
 /**
@@ -54,8 +54,8 @@ function regexArray(service, url, config, frontend) {
  * @param {boolean} forceRedirection
  */
 async function redirectAsync(url, type, initiator, forceRedirection) {
-	await init()
-	return redirect(url, type, initiator, forceRedirection)
+  await init()
+  return redirect(url, type, initiator, forceRedirection)
 }
 
 /**
@@ -65,458 +65,481 @@ async function redirectAsync(url, type, initiator, forceRedirection) {
  * @returns {undefined|string}
  */
 function rewrite(url, frontend, randomInstance) {
-	switch (frontend) {
-		case "hyperpipe":
-			return `${randomInstance}${url.pathname}${url.search}`.replace(/\/search\?q=.*/, searchQuery => searchQuery.replace("?q=", "/"))
-		case "searx":
-		case "searxng":
-			return `${randomInstance}/${url.search}`
-		case "whoogle":
-			return `${randomInstance}/search${url.search}`
-		case "4get": {
-			const s = url.searchParams.get("q")
-			if (s !== null) return `${randomInstance}/web?s=${encodeURIComponent(s)}`
-			return randomInstance
-		}
-		case "librey":
-			return `${randomInstance}/search.php${url.search}`
-		case "yattee":
-			url.searchParams.delete("si")
-			return url.href.replace(/^https?:\/{2}/, "yattee://")
-		case "freetube":
-			url.searchParams.delete("si")
-			return 'freetube://' + url.href
-		case "freetubePwa":
-			url.searchParams.delete("si")
-			return 'freetube://' + url.href
-		case "poketube": {
-			url.searchParams.delete("si")
-			if (url.pathname.startsWith('/channel')) {
-				const reg = /\/channel\/(.*)\/?$/.exec(url.pathname)
-				if (reg) {
-					const id = reg[1]
-					return `${randomInstance}/channel?id=${id}${url.search}`
-				}
-			}
-			if (/\/@[a-z]+\//.exec(url.pathname)) return randomInstance
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "libMedium":
-		case "scribe": {
-			const regex = url.hostname.match(/^(link|cdn-images-\d+|.*)\.medium\.com/)
-			if (regex && regex.length > 1) {
-				const subdomain = regex[1]
-				if (subdomain != "link" || !subdomain.startsWith("cdn-images")) {
-					return `${randomInstance}/@${subdomain}${url.pathname}${url.search}`
-				}
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "simplyTranslate":
-			return `${randomInstance}/${url.search}`
-		case "send":
-		case "mozhi":
-			return randomInstance
-		case "libreTranslate":
-			return `${randomInstance}/${url.search.replace("sl", "source").replace("tl", "target").replace("text", "q")}`
-		case "osm": {
-			const placeRegex = /\/place\/(.*?)\//
-			function convertMapCentre(url) {
-				let [lat, lon, zoom] = [null, null, null]
-				const reg = url.pathname.match(/@(-?\d[0-9.]*),(-?\d[0-9.]*),(\d{1,2})[.z]/)
-				if (reg) {
-					[, lon, lat, zoom] = reg
-				} else if (url.searchParams.has("center")) {
-					// Set map centre if present
-					[lat, lon] = url.searchParams.get("center").split(",")
-					zoom = url.searchParams.get("zoom") ?? "17"
-				}
-				return { zoom, lon, lat }
-			}
-			function addressToLatLng(address) {
-				const http = new XMLHttpRequest()
-				http.open("GET", `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(address)}&format=json&limit=1`, false)
-				http.send()
-				if (http.status == 200) {
-					const json = JSON.parse(http.responseText)[0]
-					if (json) {
-						return {
-							coordinate: `${json.lat},${json.lon}`,
-							boundingbox: `${json.boundingbox[2]},${json.boundingbox[1]},${json.boundingbox[3]},${json.boundingbox[0]}`
-						}
-					}
-					return {}
-				}
-			}
-			function getQuery(url) {
-				let query = ""
-				if (url.searchParams.has("q")) query = url.searchParams.get("q")
-				else if (url.searchParams.has("query")) query = url.searchParams.has("query")
-				return query
-			}
-			function prefsEncoded(prefs) {
-				return new URLSearchParams(prefs).toString()
-			}
-
-			if (initiator && initiator.host === "earth.google.com") return randomInstance
-
-			let mapCentre = "#"
-			let prefs = { layers: "mapnik" }
-
-			const mapCentreData = convertMapCentre(url)
-			if (mapCentreData.zoom && mapCentreData.lon && mapCentreData.lat) mapCentre = `#map=${mapCentreData.zoom}/${mapCentreData.lon}/${mapCentreData.lat}`
-
-			if (url.pathname.includes("/embed")) { // https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France
-				const query = getQuery(url)
-				let { coordinate, boundingbox } = addressToLatLng(query)
-				prefs.bbox = boundingbox
-				prefs.marker = coordinate
-				return `${randomInstance}/export/embed.html?${prefsEncoded(prefs)}`
-			} else if (url.pathname.includes("/dir")) {
-				if (url.searchParams.has("travelmode")) {
-					const travelModes = {
-						driving: "fossgis_osrm_car",
-						walking: "fossgis_osrm_foot",
-						bicycling: "fossgis_osrm_bike",
-						transit: "fossgis_osrm_car", // not implemented on OSM, default to car.
-					}
-					prefs.engine = travelModes[url.searchParams.get("travelmode")]
-				}
-				const regex1 = /\/dir\/([^@/]+)\/([^@/]+)\/@-?\d[0-9.]*,-?\d[0-9.]*,\d{1,2}[.z]/.exec(url.pathname)
-				const regex2 = /\/dir\/([^@/]+)\//.exec(url.pathname)
-				if (regex1) { // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/M%C3%A9dip%C3%B4le+Lyon-Villeurbanne/@45.760254,4.8486298,13z?travelmode=bicycling
-					const origin = addressToLatLng(decodeURIComponent(regex1[1])).coordinate ?? ''
-					const destination = addressToLatLng(decodeURIComponent(regex1[2])).coordinate ?? ''
-					prefs.route = `${origin};${destination}`
-				} else if (regex2) { // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/@45.760254,4.8486298,13z?travelmode=bicycling
-					const origin = addressToLatLng(decodeURIComponent(regex2[1])).coordinate ?? ''
-					prefs.route = `${origin};`
-				} else { // https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling
-					const origin = addressToLatLng(url.searchParams.get("origin")).coordinate ?? ''
-					const destination = addressToLatLng(url.searchParams.get("destination")).coordinate ?? ''
-					prefs.route = `${origin};${destination}`
-				}
-				return `${randomInstance}/directions?${prefsEncoded(prefs)}${mapCentre}`
-			} else if (url.pathname.match(placeRegex)) { // https://www.google.com/maps/place/H%C3%B4tel+de+Londres+Eiffel/@40.9845265,28.7081268,14z
-				const query = url.pathname.match(placeRegex)[1]
-				return `${randomInstance}/search?query=${query}${mapCentre}`
-			} else if (url.searchParams.has("ll")) { // https://maps.google.com/?ll=38.882147,-76.99017
-				const [mlat, mlon] = url.searchParams.get("ll").split(",")
-				return `${randomInstance}/search?query=${mlat}%2C${mlon}`
-			} else if (url.searchParams.has("viewpoint")) { // https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80
-				const [mlat, mlon] = url.searchParams.get("viewpoint").split(",")
-				return `${randomInstance}/search?query=${mlat}%2C${mlon}`
-			} else {
-				const query = getQuery(url)
-				if (query) return `${randomInstance}/search?query="${query}${mapCentre}&${prefsEncoded(prefs)}`
-			}
-			return `${randomInstance}/${mapCentre}&${prefsEncoded(prefs)}`
-		}
-		case "breezeWiki": {
-			let wiki, urlpath = ""
-			if (url.hostname.match(/^[a-zA-Z0-9-]+\.(?:fandom|wikia)\.com/)) {
-				wiki = url.hostname.match(/^[a-zA-Z0-9-]+(?=\.(?:fandom|wikia)\.com)/)
-				if (wiki == "www" || !wiki) wiki = ""
-				else wiki = `/${wiki}`
-				urlpath = url.pathname
-			} else {
-				wiki = url.pathname.match(/(?<=wiki\/w:c:)[a-zA-Z0-9-]+(?=:)/)
-				if (!wiki) wiki = ""
-				else {
-					wiki = "/" + wiki + "/wiki/"
-					urlpath = url.pathname.match(/(?<=wiki\/w:c:[a-zA-Z0-9-]+:).+/)
-				}
-			}
-			if (url.href.search(/Special:Search\?query/) > -1) {
-				return `${randomInstance}${wiki}${urlpath}${url.search}`.replace(/Special:Search\?query/, "search?q").replace(/\/wiki/, "")
-			}
-			return `${randomInstance}${wiki}${urlpath}${url.search}`
-		}
-		case "rimgo":
-			if (url.href.search(/^https?:\/{2}(?:[im]\.)?stack\./) > -1) return `${randomInstance}/stack${url.pathname}${url.search}`
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "redlib":
-		case "libreddit": {
-			const subdomain = url.hostname.match(/^(?:(?:external-)?preview|i)(?=\.redd\.it)/)
-			if (!subdomain) return `${randomInstance}${url.pathname}${url.search}`
-			switch (subdomain[0]) {
-				case "preview":
-					return `${randomInstance}/preview/pre${url.pathname}${url.search}`
-				case "external-preview":
-					return `${randomInstance}/preview/external-pre${url.pathname}${url.search}`
-				case "i":
-					return `${randomInstance}/img${url.pathname}`
-			}
-			return randomInstance
-		}
-		case "teddit":
-			if (/^(?:(?:external-)?preview|i)\.redd\.it/.test(url.hostname)) {
-				if (url.search == "") return `${randomInstance}${url.pathname}?teddit_proxy=${url.hostname}`
-				else return `${randomInstance}${url.pathname}${url.search}&teddit_proxy=${url.hostname}`
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "neuters": {
-			const p = url.pathname
-			if (p.startsWith('/article/') || p.startsWith('/pf/') || p.startsWith('/arc/') || p.startsWith('/resizer/')) {
-				return randomInstance
-			}
-			return `${randomInstance}${p}`
-		}
-		case "dumb":
-			if (url.pathname.endsWith('-lyrics')) return `${randomInstance}${url.pathname}`
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "intellectual":
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "ruralDictionary":
-			if (!url.pathname.includes('/define.php') && !url.pathname.includes('/random.php') && url.pathname != '/') return randomInstance
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "anonymousOverflow": {
-			if (url.hostname == "stackoverflow.com") {
-				const threadID = /^\/a\/(\d+)\/?/.exec(url.pathname)
-				if (threadID) return `${randomInstance}/questions/${threadID[1]}${url.search}`
-				return `${randomInstance}${url.pathname}${url.search}`
-			}
-			if (url.pathname == "/" || url.pathname == "") {
-				// https://stackexchange.com or https://superuser.com
-				return `${randomInstance}${url.pathname}${url.search}`
-			}
-			const regex = url.href.match(/https?:\/{2}(?:([a-zA-Z0-9-]+)\.)?stackexchange\.com\//)
-			if (regex && regex.length > 1) {
-				const subdomain = regex[1]
-				return `${randomInstance}/exchange/${subdomain}${url.pathname}${url.search}`
-			}
-			const notExchangeRegex = url.hostname.match(/(?:[a-zA-Z]+\.)?(?:askubuntu\.com|mathoverflow\.net|serverfault\.com|stackapps\.com|superuser\.com|stackoverflow\.com)/)
-			if (notExchangeRegex) {
-				return `${randomInstance}/exchange/${notExchangeRegex[0]}${url.pathname}${url.search}`
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "biblioReads":
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "wikiless": {
-			let hostSplit = url.host.split(".")
-			// wikiless doesn't have mobile view support yet
-			if (hostSplit[0] != "wikipedia" && hostSplit[0] != "www") {
-				if (hostSplit[0] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile")
-				else url.searchParams.append("lang", hostSplit[0])
-				if (hostSplit[1] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile")
-			}
-			return `${randomInstance}${url.pathname}${url.search}${url.hash}`
-		}
-		case "proxiTok":
-			if (url.pathname.startsWith('/email')) return randomInstance
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "waybackClassic": {
-			const regex = /^\/\web\/(?:[0-9]+)?\*\/(.*)/.exec(url.pathname)
-			if (regex) {
-				const link = regex[1]
-				return `${randomInstance}/cgi-bin/history.cgi?utf8=✓&q=${encodeURIComponent(link)}`
-			}
-			const regex2 = /(^\/\web\/([0-9]+)\/.*)/.exec(url.pathname)
-			if (regex2) {
-				let link = regex2[1]
-				link = link.replace(regex2[2], regex2[2] + 'if_')
-				return `https://web.archive.org${link}`
-			}
-			return
-		}
-		case "gothub":
-			if (url.hostname == "gist.github.com") return `${randomInstance}/gist${url.pathname}${url.search}`
-			if (url.hostname == "raw.githubusercontent.com") return `${randomInstance}/raw${url.pathname}${url.search}`
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "mikuInvidious":
-			if (url.hostname == "bilibili.com" || url.hostname == "www.bilibili.com" || url.hostname == 'b23.tv') return `${randomInstance}${url.pathname}${url.search}`
-			if (url.hostname == "space.bilibili.com") return `${randomInstance}/space${url.pathname}${url.search}`
-		case "tent": {
-			if (url.hostname == 'bandcamp.com' && url.pathname == '/search') {
-				const query = url.searchParams.get('q')
-				return `${randomInstance}/search.php?query=${encodeURIComponent(query)}`
-			}
-			if (url.hostname.endsWith('bandcamp.com')) {
-				const regex = /^(.*)\.bandcamp\.com/.exec(url.hostname)
-				const artist = regex[1]
-				if (url.pathname == '/' || url.pathname == '/music') {
-					return `${randomInstance}/artist.php?name=${artist}`
-				} else {
-					const regex = /^\/(.*)\/(.*)/.exec(url.pathname)
-					if (regex) {
-						const type = regex[1]
-						const name = regex[2]
-						return `${randomInstance}/release.php?artist=${artist}&type=${type}&name=${name}`
-					}
-				}
-			}
-			if (url.hostname == 'f4.bcbits.com') {
-				const regex = /\/img\/(.*)/.exec(url.pathname)
-				const image = regex[1]
-				return `${randomInstance}/image.php?file=${image}`
-			}
-			if (url.hostname == 't4.bcbits.com') {
-				const regex = /\/stream\/(.*)\/(.*)\/(.*)/.exec(url.pathname)
-				if (regex) {
-					const directory = regex[1]
-					const format = regex[2]
-					const file = regex[3]
-					const token = url.searchParams.get('token')
-					return `${randomInstance}/audio.php/?directory=${directory}&format=${format}&file=${file}&token=${encodeURIComponent(token)}`
-				}
-			}
-		}
-		case "binternet":
-			if (url.hostname == "i.pinimg.com") return `${randomInstance}/image_proxy.php?url=${url.href}`
-		case "laboratory": {
-			let path = url.pathname
-			if (path == "/") path = ""
-			return `${randomInstance}/${url.hostname}${path}${url.search}`
-		}
-		case "quetre": {
-			const regex = /([a-z]+)\.quora\.com/.exec(url.hostname)
-			if (regex) {
-				const lang = regex[1]
-				url.searchParams.append("lang", lang)
-				return `${randomInstance}${url.pathname}${url.search}`
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "pixivFe": {
-			const regex = /\/[a-z]{1,3}\/(.*)/.exec(url.pathname)
-			if (regex) {
-				const path = regex[1]
-				return `${randomInstance}/${path}${url.search}`
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "invidious": {
-			url.searchParams.delete("si")
-			if (url.hostname == "youtu.be" || url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live")) {
-				const watch = url.pathname.substring(url.pathname.lastIndexOf('/') + 1)
-				return `${randomInstance}/watch?v=${watch}${url.search.replace("?", "&")}`
-			}
-			if (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/redirect?"))
-				return url.href
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "freetubeMusic": {
-			if (url.hostname == "youtu.be" || url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live")) {
-				const watch = url.pathname.substring(url.pathname.lastIndexOf('/') + 1)
-				return `freetube://youtube.com/watch?v=${watch}`
-			}
-			return 'freetube://' + url.href
-		}
-		case "invidiousMusic": {
-			if (url.hostname == "youtu.be" || url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live")) {
-				const watch = url.pathname.substring(url.pathname.lastIndexOf('/') + 1)
-				return `${randomInstance}/watch?v=${watch}`
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case 'materialious': {
-			url.searchParams.delete('si')
-			if (url.hostname == 'youtu.be' || (url.hostname.endsWith('youtube.com') && url.pathname.startsWith('/live'))) {
-				const watch = url.pathname.substring(url.pathname.lastIndexOf('/') + 1)
-				return `${randomInstance}/watch/${watch}${url.search.replace('?', '&')}`
-			}
-			if (url.hostname.endsWith("youtube.com")) {
-				if (url.pathname.startsWith('/watch')) {
-					if (url.searchParams.has('v')) {
-						const watch = url.searchParams.get('v')
-						url.searchParams.delete('v')
-						return `${randomInstance}/watch/${watch}${url.search.replace('?', '&')}`
-					}
-					return `${randomInstance}/watch/${url.search.replace('?', '&')}`
-				}
-				if (url.pathname.startsWith('/results')) {
-					if (url.searchParams.has('search_query')) {
-						const search = url.searchParams.get('search_query')
-						url.searchParams.delete('search_query')
-						return `${randomInstance}/search/${search}${url.search.replace('?', '&')}`
-					}
-					return `${randomInstance}/search/${url.search.replace('?', '&')}`
-				}
-				if (url.pathname.startsWith('/redirect?')) {
-					return url.href
-				}
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "libremdb": {
-			if (url.pathname.startsWith("/Name")) {
-				for (const [key, value] of url.searchParams.entries()) {
-					return `${randomInstance}/title/${encodeURIComponent(key)}`
-				}
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "tuboYoutube":
-			url.searchParams.delete("si")
-			if (url.pathname.startsWith("/channel")) return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}`
-			if (url.pathname.startsWith("/watch")) return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}`
-			return randomInstance
-		case "tuboSoundcloud":
-			if (url.pathname == '/') return `${randomInstance}?kiosk?serviceId=1`
-			if (url.pathname.match(/^\/[^\/]+(\/$|$)/)) return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}`
-			if (url.pathname.match(/^\/[^\/]+\/[^\/]+/)) return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}`
-			return randomInstance
-		case "twineo":
-		case "safetwitch":
-			if (url.hostname.startsWith("clips.")) return `${randomInstance}/clip${url.pathname}${url.search}`
-			return `${randomInstance}${url.pathname}${url.search}`
-
-		case "tekstoLibre":
-			return `${randomInstance}/?${url.pathname.slice(1)}`;
-		case "skyview":
-			if (url.pathname == '/') return randomInstance
-			return `${randomInstance}?url=${encodeURIComponent(url.href)}`
-		case "nitter": {
-			let search = new URLSearchParams(url.search)
-
-			search.delete("ref_src")
-			search.delete("ref_url")
-			search.delete("s") // type of device that shared the link
-			search.delete("t") // some sort of tracking ID
-
-			search = search.toString()
-			if (search !== "") search = `?${search}`
-
-			if (url.host.split(".")[0] === "pbs" || url.host.split(".")[0] === "video") {
-				try {
-					const [, id, format, extra] = search.match(/(.*)\?format=(.*)&(.*)/)
-					const query = encodeURIComponent(`${id}.${format}?${extra}`)
-					return `${randomInstance}/pic${url.pathname}${query}`
-				} catch {
-					return `${randomInstance}/pic${url.pathname}${search}`
-				}
-			}
-			if (url.pathname.split("/").includes("tweets")) return `${randomInstance}${url.pathname.replace("/tweets", "")}${search}`
-			if (url.host == "t.co") return `${randomInstance}/t.co${url.pathname}`
-			return `${randomInstance}${url.pathname}${search}#m`
-		}
-		case "priviblur": {
-			if (url.hostname == "www.tumblr.com") return `${randomInstance}${url.pathname}${url.search}`
-			if (url.hostname.startsWith("assets")) return `${randomInstance}/tblr/assets${url.pathname}${url.search}`
-			if (url.hostname.startsWith("static")) return `${randomInstance}/tblr/static${url.pathname}${url.search}`
-
-			const reg = /^([0-9]+)\.media\.tumblr\.com/.exec(url.hostname) // *.media.tumblr.com
-			if (reg) return `${randomInstance}/tblr/media/${reg[1]}${url.pathname}${url.search}`
-
-			const blogregex = /^(?:www\.)?([a-z\d-]+)\.tumblr\.com/.exec(url.hostname) // <blog>.tumblr.com
-			if (blogregex) {
-				const blog_name = blogregex[1];
-				// Under the <blog>.tumblr.com domain posts are under a /post path
-				if (url.pathname.startsWith("/post")) return `${randomInstance}/${blog_name}${url.pathname.slice(5)}${url.search}`
-				else return `${randomInstance}/${blog_name}${url.pathname}${url.search}`;
-			}
-			return `${randomInstance}${url.pathname}${url.search}`;
-		}
-		case "piped":
-		case "pipedMaterial":
-		case "cloudtube":
-		case "lightTube":
-		case "viewtube":
-			url.searchParams.delete("si")
-		default:
-			return `${randomInstance}${url.pathname}${url.search}`
-	}
+  switch (frontend) {
+    case "hyperpipe":
+      return `${randomInstance}${url.pathname}${url.search}`.replace(/\/search\?q=.*/, searchQuery =>
+        searchQuery.replace("?q=", "/")
+      )
+    case "searx":
+    case "searxng":
+      return `${randomInstance}/${url.search}`
+    case "whoogle":
+      return `${randomInstance}/search${url.search}`
+    case "4get": {
+      const s = url.searchParams.get("q")
+      if (s !== null) return `${randomInstance}/web?s=${encodeURIComponent(s)}`
+      return randomInstance
+    }
+    case "librey":
+      return `${randomInstance}/search.php${url.search}`
+    case "yattee":
+      url.searchParams.delete("si")
+      return url.href.replace(/^https?:\/{2}/, "yattee://")
+    case "freetube":
+      url.searchParams.delete("si")
+      return "freetube://" + url.href
+    case "freetubePwa":
+      url.searchParams.delete("si")
+      return "freetube://" + url.href
+    case "poketube": {
+      url.searchParams.delete("si")
+      if (url.pathname.startsWith("/channel")) {
+        const reg = /\/channel\/(.*)\/?$/.exec(url.pathname)
+        if (reg) {
+          const id = reg[1]
+          return `${randomInstance}/channel?id=${id}${url.search}`
+        }
+      }
+      if (/\/@[a-z]+\//.exec(url.pathname)) return randomInstance
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "libMedium":
+    case "scribe": {
+      const regex = url.hostname.match(/^(link|cdn-images-\d+|.*)\.medium\.com/)
+      if (regex && regex.length > 1) {
+        const subdomain = regex[1]
+        if (subdomain != "link" || !subdomain.startsWith("cdn-images")) {
+          return `${randomInstance}/@${subdomain}${url.pathname}${url.search}`
+        }
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "simplyTranslate":
+      return `${randomInstance}/${url.search}`
+    case "send":
+    case "mozhi":
+      return randomInstance
+    case "libreTranslate":
+      return `${randomInstance}/${url.search.replace("sl", "source").replace("tl", "target").replace("text", "q")}`
+    case "osm": {
+      const placeRegex = /\/place\/(.*?)\//
+      function convertMapCentre(url) {
+        let [lat, lon, zoom] = [null, null, null]
+        const reg = url.pathname.match(/@(-?\d[0-9.]*),(-?\d[0-9.]*),(\d{1,2})[.z]/)
+        if (reg) {
+          ;[, lon, lat, zoom] = reg
+        } else if (url.searchParams.has("center")) {
+          // Set map centre if present
+          ;[lat, lon] = url.searchParams.get("center").split(",")
+          zoom = url.searchParams.get("zoom") ?? "17"
+        }
+        return { zoom, lon, lat }
+      }
+      function addressToLatLng(address) {
+        const http = new XMLHttpRequest()
+        http.open(
+          "GET",
+          `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(address)}&format=json&limit=1`,
+          false
+        )
+        http.send()
+        if (http.status == 200) {
+          const json = JSON.parse(http.responseText)[0]
+          if (json) {
+            return {
+              coordinate: `${json.lat},${json.lon}`,
+              boundingbox: `${json.boundingbox[2]},${json.boundingbox[1]},${json.boundingbox[3]},${json.boundingbox[0]}`,
+            }
+          }
+          return {}
+        }
+      }
+      function getQuery(url) {
+        let query = ""
+        if (url.searchParams.has("q")) query = url.searchParams.get("q")
+        else if (url.searchParams.has("query")) query = url.searchParams.has("query")
+        return query
+      }
+      function prefsEncoded(prefs) {
+        return new URLSearchParams(prefs).toString()
+      }
+
+      if (initiator && initiator.host === "earth.google.com") return randomInstance
+
+      let mapCentre = "#"
+      let prefs = { layers: "mapnik" }
+
+      const mapCentreData = convertMapCentre(url)
+      if (mapCentreData.zoom && mapCentreData.lon && mapCentreData.lat)
+        mapCentre = `#map=${mapCentreData.zoom}/${mapCentreData.lon}/${mapCentreData.lat}`
+
+      if (url.pathname.includes("/embed")) {
+        // https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France
+        const query = getQuery(url)
+        let { coordinate, boundingbox } = addressToLatLng(query)
+        prefs.bbox = boundingbox
+        prefs.marker = coordinate
+        return `${randomInstance}/export/embed.html?${prefsEncoded(prefs)}`
+      } else if (url.pathname.includes("/dir")) {
+        if (url.searchParams.has("travelmode")) {
+          const travelModes = {
+            driving: "fossgis_osrm_car",
+            walking: "fossgis_osrm_foot",
+            bicycling: "fossgis_osrm_bike",
+            transit: "fossgis_osrm_car", // not implemented on OSM, default to car.
+          }
+          prefs.engine = travelModes[url.searchParams.get("travelmode")]
+        }
+        const regex1 = /\/dir\/([^@/]+)\/([^@/]+)\/@-?\d[0-9.]*,-?\d[0-9.]*,\d{1,2}[.z]/.exec(url.pathname)
+        const regex2 = /\/dir\/([^@/]+)\//.exec(url.pathname)
+        if (regex1) {
+          // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/M%C3%A9dip%C3%B4le+Lyon-Villeurbanne/@45.760254,4.8486298,13z?travelmode=bicycling
+          const origin = addressToLatLng(decodeURIComponent(regex1[1])).coordinate ?? ""
+          const destination = addressToLatLng(decodeURIComponent(regex1[2])).coordinate ?? ""
+          prefs.route = `${origin};${destination}`
+        } else if (regex2) {
+          // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/@45.760254,4.8486298,13z?travelmode=bicycling
+          const origin = addressToLatLng(decodeURIComponent(regex2[1])).coordinate ?? ""
+          prefs.route = `${origin};`
+        } else {
+          // https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling
+          const origin = addressToLatLng(url.searchParams.get("origin")).coordinate ?? ""
+          const destination = addressToLatLng(url.searchParams.get("destination")).coordinate ?? ""
+          prefs.route = `${origin};${destination}`
+        }
+        return `${randomInstance}/directions?${prefsEncoded(prefs)}${mapCentre}`
+      } else if (url.pathname.match(placeRegex)) {
+        // https://www.google.com/maps/place/H%C3%B4tel+de+Londres+Eiffel/@40.9845265,28.7081268,14z
+        const query = url.pathname.match(placeRegex)[1]
+        return `${randomInstance}/search?query=${query}${mapCentre}`
+      } else if (url.searchParams.has("ll")) {
+        // https://maps.google.com/?ll=38.882147,-76.99017
+        const [mlat, mlon] = url.searchParams.get("ll").split(",")
+        return `${randomInstance}/search?query=${mlat}%2C${mlon}`
+      } else if (url.searchParams.has("viewpoint")) {
+        // https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80
+        const [mlat, mlon] = url.searchParams.get("viewpoint").split(",")
+        return `${randomInstance}/search?query=${mlat}%2C${mlon}`
+      } else {
+        const query = getQuery(url)
+        if (query) return `${randomInstance}/search?query="${query}${mapCentre}&${prefsEncoded(prefs)}`
+      }
+      return `${randomInstance}/${mapCentre}&${prefsEncoded(prefs)}`
+    }
+    case "breezeWiki": {
+      let wiki,
+        urlpath = ""
+      if (url.hostname.match(/^[a-zA-Z0-9-]+\.(?:fandom|wikia)\.com/)) {
+        wiki = url.hostname.match(/^[a-zA-Z0-9-]+(?=\.(?:fandom|wikia)\.com)/)
+        if (wiki == "www" || !wiki) wiki = ""
+        else wiki = `/${wiki}`
+        urlpath = url.pathname
+      } else {
+        wiki = url.pathname.match(/(?<=wiki\/w:c:)[a-zA-Z0-9-]+(?=:)/)
+        if (!wiki) wiki = ""
+        else {
+          wiki = "/" + wiki + "/wiki/"
+          urlpath = url.pathname.match(/(?<=wiki\/w:c:[a-zA-Z0-9-]+:).+/)
+        }
+      }
+      if (url.href.search(/Special:Search\?query/) > -1) {
+        return `${randomInstance}${wiki}${urlpath}${url.search}`
+          .replace(/Special:Search\?query/, "search?q")
+          .replace(/\/wiki/, "")
+      }
+      return `${randomInstance}${wiki}${urlpath}${url.search}`
+    }
+    case "rimgo":
+      if (url.href.search(/^https?:\/{2}(?:[im]\.)?stack\./) > -1)
+        return `${randomInstance}/stack${url.pathname}${url.search}`
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "redlib":
+    case "libreddit": {
+      const subdomain = url.hostname.match(/^(?:(?:external-)?preview|i)(?=\.redd\.it)/)
+      if (!subdomain) return `${randomInstance}${url.pathname}${url.search}`
+      switch (subdomain[0]) {
+        case "preview":
+          return `${randomInstance}/preview/pre${url.pathname}${url.search}`
+        case "external-preview":
+          return `${randomInstance}/preview/external-pre${url.pathname}${url.search}`
+        case "i":
+          return `${randomInstance}/img${url.pathname}`
+      }
+      return randomInstance
+    }
+    case "teddit":
+      if (/^(?:(?:external-)?preview|i)\.redd\.it/.test(url.hostname)) {
+        if (url.search == "") return `${randomInstance}${url.pathname}?teddit_proxy=${url.hostname}`
+        else return `${randomInstance}${url.pathname}${url.search}&teddit_proxy=${url.hostname}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "neuters": {
+      const p = url.pathname
+      if (p.startsWith("/article/") || p.startsWith("/pf/") || p.startsWith("/arc/") || p.startsWith("/resizer/")) {
+        return randomInstance
+      }
+      return `${randomInstance}${p}`
+    }
+    case "dumb":
+      if (url.pathname.endsWith("-lyrics")) return `${randomInstance}${url.pathname}`
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "intellectual":
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "ruralDictionary":
+      if (!url.pathname.includes("/define.php") && !url.pathname.includes("/random.php") && url.pathname != "/")
+        return randomInstance
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "anonymousOverflow": {
+      if (url.hostname == "stackoverflow.com") {
+        const threadID = /^\/a\/(\d+)\/?/.exec(url.pathname)
+        if (threadID) return `${randomInstance}/questions/${threadID[1]}${url.search}`
+        return `${randomInstance}${url.pathname}${url.search}`
+      }
+      if (url.pathname == "/" || url.pathname == "") {
+        // https://stackexchange.com or https://superuser.com
+        return `${randomInstance}${url.pathname}${url.search}`
+      }
+      const regex = url.href.match(/https?:\/{2}(?:([a-zA-Z0-9-]+)\.)?stackexchange\.com\//)
+      if (regex && regex.length > 1) {
+        const subdomain = regex[1]
+        return `${randomInstance}/exchange/${subdomain}${url.pathname}${url.search}`
+      }
+      const notExchangeRegex = url.hostname.match(
+        /(?:[a-zA-Z]+\.)?(?:askubuntu\.com|mathoverflow\.net|serverfault\.com|stackapps\.com|superuser\.com|stackoverflow\.com)/
+      )
+      if (notExchangeRegex) {
+        return `${randomInstance}/exchange/${notExchangeRegex[0]}${url.pathname}${url.search}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "biblioReads":
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "wikiless": {
+      let hostSplit = url.host.split(".")
+      // wikiless doesn't have mobile view support yet
+      if (hostSplit[0] != "wikipedia" && hostSplit[0] != "www") {
+        if (hostSplit[0] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile")
+        else url.searchParams.append("lang", hostSplit[0])
+        if (hostSplit[1] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile")
+      }
+      return `${randomInstance}${url.pathname}${url.search}${url.hash}`
+    }
+    case "proxiTok":
+      if (url.pathname.startsWith("/email")) return randomInstance
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "waybackClassic": {
+      const regex = /^\/\web\/(?:[0-9]+)?\*\/(.*)/.exec(url.pathname)
+      if (regex) {
+        const link = regex[1]
+        return `${randomInstance}/cgi-bin/history.cgi?utf8=✓&q=${encodeURIComponent(link)}`
+      }
+      const regex2 = /(^\/\web\/([0-9]+)\/.*)/.exec(url.pathname)
+      if (regex2) {
+        let link = regex2[1]
+        link = link.replace(regex2[2], regex2[2] + "if_")
+        return `https://web.archive.org${link}`
+      }
+      return
+    }
+    case "gothub":
+      if (url.hostname == "gist.github.com") return `${randomInstance}/gist${url.pathname}${url.search}`
+      if (url.hostname == "raw.githubusercontent.com") return `${randomInstance}/raw${url.pathname}${url.search}`
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "mikuInvidious":
+      if (url.hostname == "bilibili.com" || url.hostname == "www.bilibili.com" || url.hostname == "b23.tv")
+        return `${randomInstance}${url.pathname}${url.search}`
+      if (url.hostname == "space.bilibili.com") return `${randomInstance}/space${url.pathname}${url.search}`
+    case "tent": {
+      if (url.hostname == "bandcamp.com" && url.pathname == "/search") {
+        const query = url.searchParams.get("q")
+        return `${randomInstance}/search.php?query=${encodeURIComponent(query)}`
+      }
+      if (url.hostname.endsWith("bandcamp.com")) {
+        const regex = /^(.*)\.bandcamp\.com/.exec(url.hostname)
+        const artist = regex[1]
+        if (url.pathname == "/" || url.pathname == "/music") {
+          return `${randomInstance}/artist.php?name=${artist}`
+        } else {
+          const regex = /^\/(.*)\/(.*)/.exec(url.pathname)
+          if (regex) {
+            const type = regex[1]
+            const name = regex[2]
+            return `${randomInstance}/release.php?artist=${artist}&type=${type}&name=${name}`
+          }
+        }
+      }
+      if (url.hostname == "f4.bcbits.com") {
+        const regex = /\/img\/(.*)/.exec(url.pathname)
+        const image = regex[1]
+        return `${randomInstance}/image.php?file=${image}`
+      }
+      if (url.hostname == "t4.bcbits.com") {
+        const regex = /\/stream\/(.*)\/(.*)\/(.*)/.exec(url.pathname)
+        if (regex) {
+          const directory = regex[1]
+          const format = regex[2]
+          const file = regex[3]
+          const token = url.searchParams.get("token")
+          return `${randomInstance}/audio.php/?directory=${directory}&format=${format}&file=${file}&token=${encodeURIComponent(token)}`
+        }
+      }
+    }
+    case "binternet":
+      if (url.hostname == "i.pinimg.com") return `${randomInstance}/image_proxy.php?url=${url.href}`
+    case "laboratory": {
+      let path = url.pathname
+      if (path == "/") path = ""
+      return `${randomInstance}/${url.hostname}${path}${url.search}`
+    }
+    case "quetre": {
+      const regex = /([a-z]+)\.quora\.com/.exec(url.hostname)
+      if (regex) {
+        const lang = regex[1]
+        url.searchParams.append("lang", lang)
+        return `${randomInstance}${url.pathname}${url.search}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "pixivFe": {
+      const regex = /\/[a-z]{1,3}\/(.*)/.exec(url.pathname)
+      if (regex) {
+        const path = regex[1]
+        return `${randomInstance}/${path}${url.search}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "invidious": {
+      url.searchParams.delete("si")
+      if (url.hostname == "youtu.be" || (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live"))) {
+        const watch = url.pathname.substring(url.pathname.lastIndexOf("/") + 1)
+        return `${randomInstance}/watch?v=${watch}${url.search.replace("?", "&")}`
+      }
+      if (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/redirect?")) return url.href
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "freetubeMusic": {
+      if (url.hostname == "youtu.be" || (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live"))) {
+        const watch = url.pathname.substring(url.pathname.lastIndexOf("/") + 1)
+        return `freetube://youtube.com/watch?v=${watch}`
+      }
+      return "freetube://" + url.href
+    }
+    case "invidiousMusic": {
+      if (url.hostname == "youtu.be" || (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live"))) {
+        const watch = url.pathname.substring(url.pathname.lastIndexOf("/") + 1)
+        return `${randomInstance}/watch?v=${watch}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "materialious": {
+      url.searchParams.delete("si")
+      if (url.hostname == "youtu.be" || (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live"))) {
+        const watch = url.pathname.substring(url.pathname.lastIndexOf("/") + 1)
+        return `${randomInstance}/watch/${watch}${url.search.replace("?", "&")}`
+      }
+      if (url.hostname.endsWith("youtube.com")) {
+        if (url.pathname.startsWith("/watch")) {
+          if (url.searchParams.has("v")) {
+            const watch = url.searchParams.get("v")
+            url.searchParams.delete("v")
+            return `${randomInstance}/watch/${watch}${url.search.replace("?", "&")}`
+          }
+          return `${randomInstance}/watch/${url.search.replace("?", "&")}`
+        }
+        if (url.pathname.startsWith("/results")) {
+          if (url.searchParams.has("search_query")) {
+            const search = url.searchParams.get("search_query")
+            url.searchParams.delete("search_query")
+            return `${randomInstance}/search/${search}${url.search.replace("?", "&")}`
+          }
+          return `${randomInstance}/search/${url.search.replace("?", "&")}`
+        }
+        if (url.pathname.startsWith("/redirect?")) {
+          return url.href
+        }
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "libremdb": {
+      if (url.pathname.startsWith("/Name")) {
+        for (const [key, value] of url.searchParams.entries()) {
+          return `${randomInstance}/title/${encodeURIComponent(key)}`
+        }
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "tuboYoutube":
+      url.searchParams.delete("si")
+      if (url.pathname.startsWith("/channel")) return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}`
+      if (url.pathname.startsWith("/watch")) return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}`
+      return randomInstance
+    case "tuboSoundcloud":
+      if (url.pathname == "/") return `${randomInstance}?kiosk?serviceId=1`
+      if (url.pathname.match(/^\/[^\/]+(\/$|$)/)) return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}`
+      if (url.pathname.match(/^\/[^\/]+\/[^\/]+/)) return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}`
+      return randomInstance
+    case "twineo":
+    case "safetwitch":
+      if (url.hostname.startsWith("clips.")) return `${randomInstance}/clip${url.pathname}${url.search}`
+      return `${randomInstance}${url.pathname}${url.search}`
+
+    case "tekstoLibre":
+      return `${randomInstance}/?${url.pathname.slice(1)}`
+    case "skyview":
+      if (url.pathname == "/") return randomInstance
+      return `${randomInstance}?url=${encodeURIComponent(url.href)}`
+    case "nitter": {
+      let search = new URLSearchParams(url.search)
+
+      search.delete("ref_src")
+      search.delete("ref_url")
+      search.delete("s") // type of device that shared the link
+      search.delete("t") // some sort of tracking ID
+
+      search = search.toString()
+      if (search !== "") search = `?${search}`
+
+      if (url.host.split(".")[0] === "pbs" || url.host.split(".")[0] === "video") {
+        try {
+          const [, id, format, extra] = search.match(/(.*)\?format=(.*)&(.*)/)
+          const query = encodeURIComponent(`${id}.${format}?${extra}`)
+          return `${randomInstance}/pic${url.pathname}${query}`
+        } catch {
+          return `${randomInstance}/pic${url.pathname}${search}`
+        }
+      }
+      if (url.pathname.split("/").includes("tweets"))
+        return `${randomInstance}${url.pathname.replace("/tweets", "")}${search}`
+      if (url.host == "t.co") return `${randomInstance}/t.co${url.pathname}`
+      return `${randomInstance}${url.pathname}${search}#m`
+    }
+    case "priviblur": {
+      if (url.hostname == "www.tumblr.com") return `${randomInstance}${url.pathname}${url.search}`
+      if (url.hostname.startsWith("assets")) return `${randomInstance}/tblr/assets${url.pathname}${url.search}`
+      if (url.hostname.startsWith("static")) return `${randomInstance}/tblr/static${url.pathname}${url.search}`
+
+      const reg = /^([0-9]+)\.media\.tumblr\.com/.exec(url.hostname) // *.media.tumblr.com
+      if (reg) return `${randomInstance}/tblr/media/${reg[1]}${url.pathname}${url.search}`
+
+      const blogregex = /^(?:www\.)?([a-z\d-]+)\.tumblr\.com/.exec(url.hostname) // <blog>.tumblr.com
+      if (blogregex) {
+        const blog_name = blogregex[1]
+        // Under the <blog>.tumblr.com domain posts are under a /post path
+        if (url.pathname.startsWith("/post"))
+          return `${randomInstance}/${blog_name}${url.pathname.slice(5)}${url.search}`
+        else return `${randomInstance}/${blog_name}${url.pathname}${url.search}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "piped":
+    case "pipedMaterial":
+    case "cloudtube":
+    case "lightTube":
+    case "viewtube":
+      url.searchParams.delete("si")
+    default:
+      return `${randomInstance}${url.pathname}${url.search}`
+  }
 }
 
 /**
@@ -527,50 +550,54 @@ function rewrite(url, frontend, randomInstance) {
  * @returns {string | undefined}
  */
 function redirect(url, type, initiator, forceRedirection, incognito) {
-	if (type != "main_frame" && type != "sub_frame" && type != "image") return
-	let randomInstance
-	let frontend
-	if (!forceRedirection && options.redirectOnlyInIncognito == true && !incognito) return
-	for (const service in config.services) {
-		if (!forceRedirection && !options[service].enabled) continue
-
-		frontend = options[service].frontend
-
-		if (config.services[service].frontends[frontend].desktopApp && type != "main_frame" && options[service].redirectType != "main_frame")
-			frontend = options[service].embedFrontend
-
-		if (!regexArray(service, url, config, frontend)) {
-			frontend = null
-			continue
-		}
-
-		if (
-			config.services[service].embeddable
-			&&
-			type != options[service].redirectType && options[service].redirectType != "both"
-		) {
-			if (options[service].unsupportedUrls == 'block') return 'CANCEL'
-			return
-		}
-
-		let instanceList = options[frontend]
-		if (instanceList === undefined) break
-		if (instanceList.length === 0) return null
-
-		if (initiator && instanceList.includes(initiator.origin)) {
-			if (type != "main_frame") return null
-			else return "BYPASSTAB"
-		}
-
-		randomInstance = utils.getRandomInstance(instanceList)
-		if (config.services[service].frontends[frontend].localhost && options[service].instance == "localhost") {
-			randomInstance = `http://${frontend}.localhost:8080`
-		}
-		break
-	}
-	if (!frontend) return
-
-	return rewrite(url, frontend, randomInstance)
+  if (type != "main_frame" && type != "sub_frame" && type != "image") return
+  let randomInstance
+  let frontend
+  if (!forceRedirection && options.redirectOnlyInIncognito == true && !incognito) return
+  for (const service in config.services) {
+    if (!forceRedirection && !options[service].enabled) continue
+
+    frontend = options[service].frontend
+
+    if (
+      config.services[service].frontends[frontend].desktopApp &&
+      type != "main_frame" &&
+      options[service].redirectType != "main_frame"
+    )
+      frontend = options[service].embedFrontend
+
+    if (!regexArray(service, url, config, frontend)) {
+      frontend = null
+      continue
+    }
+
+    if (
+      config.services[service].embeddable &&
+      type != options[service].redirectType &&
+      options[service].redirectType != "both"
+    ) {
+      if (options[service].unsupportedUrls == "block") return "CANCEL"
+      return
+    }
+
+    let instanceList = options[frontend]
+    if (instanceList === undefined) break
+    if (instanceList.length === 0) return null
+
+    if (initiator && instanceList.includes(initiator.origin)) {
+      if (type != "main_frame") return null
+      else return "BYPASSTAB"
+    }
+
+    randomInstance = utils.getRandomInstance(instanceList)
+    if (config.services[service].frontends[frontend].localhost && options[service].instance == "localhost") {
+      randomInstance = `http://${frontend}.localhost:8080`
+    }
+    break
+  }
+  if (!frontend) return
+
+  return rewrite(url, frontend, randomInstance)
 }
 
 /**
@@ -578,27 +605,25 @@ function redirect(url, type, initiator, forceRedirection, incognito) {
  * @param {*} returnFrontend
  */
 function computeService(url, returnFrontend) {
-	return new Promise(async resolve => {
-		const config = await utils.getConfig()
-		const options = await utils.getOptions()
-		for (const service in config.services) {
-			if (regexArray(service, url, config)) {
-				resolve(service)
-				return
-			} else {
-				for (const frontend in config.services[service].frontends) {
-					if (all(service, frontend, options, config).includes(utils.protocolHost(url))) {
-						if (returnFrontend)
-							resolve([service, frontend, utils.protocolHost(url)])
-						else
-							resolve(service)
-						return
-					}
-				}
-			}
-		}
-		resolve()
-	})
+  return new Promise(async resolve => {
+    const config = await utils.getConfig()
+    const options = await utils.getOptions()
+    for (const service in config.services) {
+      if (regexArray(service, url, config)) {
+        resolve(service)
+        return
+      } else {
+        for (const frontend in config.services[service].frontends) {
+          if (all(service, frontend, options, config).includes(utils.protocolHost(url))) {
+            if (returnFrontend) resolve([service, frontend, utils.protocolHost(url)])
+            else resolve(service)
+            return
+          }
+        }
+      }
+    }
+    resolve()
+  })
 }
 
 /**
@@ -606,303 +631,301 @@ function computeService(url, returnFrontend) {
  * @param {string} customService
  */
 function switchInstance(url, customService) {
-	return new Promise(async resolve => {
-		let options = await utils.getOptions()
-		let config = await utils.getConfig()
-
-		const protocolHost = utils.protocolHost(url)
-		if (customService) {
-			const instancesList = options[options[customService].frontend]
-			if (instancesList !== undefined) {
-				const newInstance = utils.getNextInstance(url.origin, instancesList)
-				if (newInstance) {
-					resolve(`${newInstance}${url.pathname}${url.search}`)
-					return
-				}
-			}
-		} else {
-			for (const service in config.services) {
-				let instancesList = options[options[service].frontend]
-				if (instancesList === undefined) continue
-				if (!instancesList.includes(protocolHost)) continue
-
-				instancesList.splice(instancesList.indexOf(protocolHost), 1)
-				if (instancesList.length === 0) {
-					resolve()
-					return
-				}
-				const newInstance = utils.getNextInstance(url.origin, instancesList)
-				if (newInstance) {
-					resolve(`${newInstance}${url.pathname}${url.search}`)
-					return
-				}
-			}
-		}
-		resolve()
-	})
+  return new Promise(async resolve => {
+    let options = await utils.getOptions()
+    let config = await utils.getConfig()
+
+    const protocolHost = utils.protocolHost(url)
+    if (customService) {
+      const instancesList = options[options[customService].frontend]
+      if (instancesList !== undefined) {
+        const newInstance = utils.getNextInstance(url.origin, instancesList)
+        if (newInstance) {
+          resolve(`${newInstance}${url.pathname}${url.search}`)
+          return
+        }
+      }
+    } else {
+      for (const service in config.services) {
+        let instancesList = options[options[service].frontend]
+        if (instancesList === undefined) continue
+        if (!instancesList.includes(protocolHost)) continue
+
+        instancesList.splice(instancesList.indexOf(protocolHost), 1)
+        if (instancesList.length === 0) {
+          resolve()
+          return
+        }
+        const newInstance = utils.getNextInstance(url.origin, instancesList)
+        if (newInstance) {
+          resolve(`${newInstance}${url.pathname}${url.search}`)
+          return
+        }
+      }
+    }
+    resolve()
+  })
 }
 
 /**
  * @param {URL} url
  */
 async function reverse(url) {
-	let options = await utils.getOptions()
-	let config = await utils.getConfig()
-	let protocolHost = utils.protocolHost(url)
-	for (const service in config.services) {
-		let frontend = options[service].frontend
-		if (options[frontend] == undefined) continue
-		if (!options[frontend].includes(protocolHost) && protocolHost != `http://${frontend}.localhost:8080`) continue
-		switch (service) {
-			case "youtube":
-			case "imdb":
-			case "imgur":
-			case "tiktok":
-			case "reddit":
-			case "imdb":
-			case "snopes":
-			case "urbanDictionary":
-			case "quora":
-			case "medium":
-				return `${config.services[service].url}${url.pathname}${url.search}`
-			case "fandom": {
-				let regex = url.pathname.match(/^\/([a-zA-Z0-9-]+)\/wiki\/(.*)/)
-				if (regex) return `https://${regex[1]}.fandom.com/wiki/${regex[2]}`
-				return
-			}
-			case "wikipedia": {
-				const lang = url.searchParams.get("lang")
-				if (lang != null) {
-					return `https://${lang}.wikipedia.org${url.pathname}${url.search}${url.hash}`
-				}
-				return `https://wikipedia.org${url.pathname}${url.search}${url.hash}`
-			}
-			case "stackOverflow": {
-				if (url.pathname.startsWith("/questions/")) {
-					return `https://stackoverflow.com${url.pathname}${url.search}`
-				}
-				if (url.pathname.startsWith("/exchange/")) {
-					const regex = /\/exchange\/(.*?)(\/.*)/.exec(url.pathname)
-					if (regex) return `https://${regex[1]}.stackexchange.com${regex[2]}`
-				}
-				return
-			}
-			case "tekstowo":
-				return `${config.services[service].url}/${url.search.slice(1)}`
-			case "goodreads":
-				return `https://goodreads.com${url.pathname}${url.search}`
-			default:
-				return
-		}
-	}
-	return
+  let options = await utils.getOptions()
+  let config = await utils.getConfig()
+  let protocolHost = utils.protocolHost(url)
+  for (const service in config.services) {
+    let frontend = options[service].frontend
+    if (options[frontend] == undefined) continue
+    if (!options[frontend].includes(protocolHost) && protocolHost != `http://${frontend}.localhost:8080`) continue
+    switch (service) {
+      case "youtube":
+      case "imdb":
+      case "imgur":
+      case "tiktok":
+      case "reddit":
+      case "imdb":
+      case "snopes":
+      case "urbanDictionary":
+      case "quora":
+      case "medium":
+        return `${config.services[service].url}${url.pathname}${url.search}`
+      case "fandom": {
+        let regex = url.pathname.match(/^\/([a-zA-Z0-9-]+)\/wiki\/(.*)/)
+        if (regex) return `https://${regex[1]}.fandom.com/wiki/${regex[2]}`
+        return
+      }
+      case "wikipedia": {
+        const lang = url.searchParams.get("lang")
+        if (lang != null) {
+          return `https://${lang}.wikipedia.org${url.pathname}${url.search}${url.hash}`
+        }
+        return `https://wikipedia.org${url.pathname}${url.search}${url.hash}`
+      }
+      case "stackOverflow": {
+        if (url.pathname.startsWith("/questions/")) {
+          return `https://stackoverflow.com${url.pathname}${url.search}`
+        }
+        if (url.pathname.startsWith("/exchange/")) {
+          const regex = /\/exchange\/(.*?)(\/.*)/.exec(url.pathname)
+          if (regex) return `https://${regex[1]}.stackexchange.com${regex[2]}`
+        }
+        return
+      }
+      case "tekstowo":
+        return `${config.services[service].url}/${url.search.slice(1)}`
+      case "goodreads":
+        return `https://goodreads.com${url.pathname}${url.search}`
+      default:
+        return
+    }
+  }
+  return
 }
 
 const defaultInstances = {
-	'invidious': ['https://inv.vern.cc'],
-	'materialious': ['https://app.materialio.us'],
-	'viewtube': ['https://viewtube.io'],
-	'piped': ['https://pipedapi-libre.kavin.rocks'],
-	'pipedMaterial': ['https://piped-material.xn--17b.net'],
-	'cloudtube': ['https://tube.cadence.moe'],
-	'lightTube': ['https://tube.kuylar.dev'],
-	'poketube': ['https://poketube.fun'],
-	'proxiTok': ['https://proxitok.pabloferreiro.es'],
-	'redlib': ['https://safereddit.com'],
-	'libreddit': ['https://libreddit.spike.codes'],
-	'teddit': ['https://teddit.net'],
-	'scribe': ['https://scribe.rip'],
-	'libMedium': ['https://md.vern.cc'],
-	'quetre': ['https://quetre.iket.me'],
-	'libremdb': ['https://libremdb.iket.me'],
-	'simplyTranslate': ['https://simplytranslate.org'],
-	'mozhi': ['https://mozhi.aryak.me'],
-	'searxng': ['https://search.bus-hit.me'],
-	'4get': ['https://4get.ca'],
-	'rimgo': ['https://rimgo.vern.cc'],
-	'hyperpipe': ['https://hyperpipe.surge.sh'],
-	'osm': ['https://www.openstreetmap.org'],
-	'breezeWiki': ['https://breezewiki.com'],
-	'neuters': ['https://neuters.de'],
-	'dumb': ['https://dm.vern.cc'],
-	"intellectual": ['https://intellectual.insprill.net'],
-	'ruralDictionary': ['https://rd.vern.cc'],
-	'anonymousOverflow': ['https://code.whatever.social'],
-	'biblioReads': ['https://biblioreads.ml'],
-	'wikiless': ['https://wikiless.org'],
-	'suds': ['https://sd.vern.cc'],
-	'unfunny': ['https://uf.vern.cc'],
-	'soprano': ['https://sp.vern.cc'],
-	'meme': ['https://mm.vern.cc'],
-	'waybackClassic': ['https://wayback-classic.net'],
-	'gothub': ['https://gh.odyssey346.dev'],
-	'mikuInvidious': ['https://mikuinv.resrv.org'],
-	"tent": ['https://tent.sny.sh'],
-	"wolfreeAlpha": ['https://gqq.gitlab.io', 'https://uqq.gitlab.io'],
-	"laboratory": ['https://lab.vern.cc'],
-	'binternet': ['https://bn.bloat.cat'],
-	'pixivFe': ['https://pixivfe.exozy.me'],
-	'indestructables': ['https://indestructables.private.coffee'],
-	'destructables': ['https://ds.vern.cc'],
-	'safetwitch': ['https://safetwitch.drgns.space'],
-	'twineo': ['https://twineo.exozy.me'],
-	'proxigram': ['https://ig.opnxng.com'],
-	'tuboYoutube': ['https://tubo.migalmoreno.com'],
-	'tuboSoundcloud': ['https://tubo.migalmoreno.com'],
-	'tekstoLibre': ['https://davilarek.github.io/TekstoLibre'],
-	'skyview': ['https://skyview.social'],
-	'priviblur': ['https://pb.bloat.cat'],
-	'nitter': ['https://nitter.privacydev.net'],
-	'pasted': ['https://pasted.drakeerv.com'],
+  invidious: ["https://inv.vern.cc"],
+  materialious: ["https://app.materialio.us"],
+  viewtube: ["https://viewtube.io"],
+  piped: ["https://pipedapi-libre.kavin.rocks"],
+  pipedMaterial: ["https://piped-material.xn--17b.net"],
+  cloudtube: ["https://tube.cadence.moe"],
+  lightTube: ["https://tube.kuylar.dev"],
+  poketube: ["https://poketube.fun"],
+  proxiTok: ["https://proxitok.pabloferreiro.es"],
+  redlib: ["https://safereddit.com"],
+  libreddit: ["https://libreddit.spike.codes"],
+  teddit: ["https://teddit.net"],
+  scribe: ["https://scribe.rip"],
+  libMedium: ["https://md.vern.cc"],
+  quetre: ["https://quetre.iket.me"],
+  libremdb: ["https://libremdb.iket.me"],
+  simplyTranslate: ["https://simplytranslate.org"],
+  mozhi: ["https://mozhi.aryak.me"],
+  searxng: ["https://search.bus-hit.me"],
+  "4get": ["https://4get.ca"],
+  rimgo: ["https://rimgo.vern.cc"],
+  hyperpipe: ["https://hyperpipe.surge.sh"],
+  osm: ["https://www.openstreetmap.org"],
+  breezeWiki: ["https://breezewiki.com"],
+  neuters: ["https://neuters.de"],
+  dumb: ["https://dm.vern.cc"],
+  intellectual: ["https://intellectual.insprill.net"],
+  ruralDictionary: ["https://rd.vern.cc"],
+  anonymousOverflow: ["https://code.whatever.social"],
+  biblioReads: ["https://biblioreads.ml"],
+  wikiless: ["https://wikiless.org"],
+  suds: ["https://sd.vern.cc"],
+  unfunny: ["https://uf.vern.cc"],
+  soprano: ["https://sp.vern.cc"],
+  meme: ["https://mm.vern.cc"],
+  waybackClassic: ["https://wayback-classic.net"],
+  gothub: ["https://gh.odyssey346.dev"],
+  mikuInvidious: ["https://mikuinv.resrv.org"],
+  tent: ["https://tent.sny.sh"],
+  wolfreeAlpha: ["https://gqq.gitlab.io", "https://uqq.gitlab.io"],
+  laboratory: ["https://lab.vern.cc"],
+  binternet: ["https://bn.bloat.cat"],
+  pixivFe: ["https://pixivfe.exozy.me"],
+  indestructables: ["https://indestructables.private.coffee"],
+  destructables: ["https://ds.vern.cc"],
+  safetwitch: ["https://safetwitch.drgns.space"],
+  twineo: ["https://twineo.exozy.me"],
+  proxigram: ["https://ig.opnxng.com"],
+  tuboYoutube: ["https://tubo.migalmoreno.com"],
+  tuboSoundcloud: ["https://tubo.migalmoreno.com"],
+  tekstoLibre: ["https://davilarek.github.io/TekstoLibre"],
+  skyview: ["https://skyview.social"],
+  priviblur: ["https://pb.bloat.cat"],
+  nitter: ["https://nitter.privacydev.net"],
+  pasted: ["https://pasted.drakeerv.com"],
 }
 
 function initDefaults() {
-	return new Promise(resolve => {
-		browser.storage.local.clear(async () => {
-			let config = await utils.getConfig()
-			let options = {}
-			for (const service in config.services) {
-				options[service] = {}
-				for (const defaultOption in config.services[service].options) {
-					options[service][defaultOption] = config.services[service].options[defaultOption]
-				}
-				for (const frontend in config.services[service].frontends) {
-					if (config.services[service].frontends[frontend].instanceList) {
-						options[frontend] = []
-					}
-				}
-			}
-			options.exceptions = {
-				url: [],
-				regex: [],
-			}
-			options.theme = "detect"
-			options.popupServices = ["youtube", "tiktok", "imgur", "reddit", "quora", "translate", "maps"]
-			options.fetchInstances = 'github'
-			options.redirectOnlyInIncognito = false
-
-			options = { ...options, ...defaultInstances }
-
-			browser.storage.local.set({ options },
-				() => resolve()
-			)
-		})
-	})
+  return new Promise(resolve => {
+    browser.storage.local.clear(async () => {
+      let config = await utils.getConfig()
+      let options = {}
+      for (const service in config.services) {
+        options[service] = {}
+        for (const defaultOption in config.services[service].options) {
+          options[service][defaultOption] = config.services[service].options[defaultOption]
+        }
+        for (const frontend in config.services[service].frontends) {
+          if (config.services[service].frontends[frontend].instanceList) {
+            options[frontend] = []
+          }
+        }
+      }
+      options.exceptions = {
+        url: [],
+        regex: [],
+      }
+      options.theme = "detect"
+      options.popupServices = ["youtube", "tiktok", "imgur", "reddit", "quora", "translate", "maps"]
+      options.fetchInstances = "github"
+      options.redirectOnlyInIncognito = false
+
+      options = { ...options, ...defaultInstances }
+
+      browser.storage.local.set({ options }, () => resolve())
+    })
+  })
 }
 
 function upgradeOptions() {
-	return new Promise(async resolve => {
-		let options = await utils.getOptions()
-
-		browser.storage.local.clear(() => {
-			browser.storage.local.set({ options }, () => {
-				resolve()
-			})
-		})
-	})
+  return new Promise(async resolve => {
+    let options = await utils.getOptions()
+
+    browser.storage.local.clear(() => {
+      browser.storage.local.set({ options }, () => {
+        resolve()
+      })
+    })
+  })
 }
 
 function processUpdate() {
-	return new Promise(async resolve => {
-		let frontends = []
-		const config = await utils.getConfig()
-		let options = await utils.getOptions()
-		for (const service in config.services) {
-			if (!options[service]) options[service] = {}
-
-			if (!(options[service].frontend in config.services[service].frontends)) {
-				options[service] = config.services[service].options // Reset settings for service
-				delete options[options[service].frontend] // Remove deprecated frontend
-			}
-
-			for (const defaultOption in config.services[service].options) {
-				if (!(defaultOption in options[service])) {
-					options[service][defaultOption] = config.services[service].options[defaultOption]
-				}
-			}
-
-			for (const frontend in config.services[service].frontends) {
-				frontends.push(frontend)
-				if (!(frontend in options) && config.services[service].frontends[frontend].instanceList) {
-					options[frontend] = defaultInstances[frontend] || []
-				}
-			}
-
-			for (const frontend of options.popupServices) {
-				if (!Object.keys(config.services).includes(frontend)) {
-					const i = options.popupServices.indexOf(frontend);
-					if (i > -1) options.popupServices.splice(i, 1);
-				}
-			}
-		}
-		const general = ['theme', 'popupServices', 'fetchInstances', 'redirectOnlyInIncognito']
-		const combined = [
-			...Object.keys(config.services),
-			...frontends,
-			...general,
-			'exceptions',
-			'popupServices',
-			'version',
-		]
-		for (const key in options) {
-			if (combined.indexOf(key) < 0) {
-				delete options[key] // Remove any unknown settings in options
-			}
-		}
-		browser.storage.local.set({ options }, () => {
-			resolve()
-		})
-	})
+  return new Promise(async resolve => {
+    let frontends = []
+    const config = await utils.getConfig()
+    let options = await utils.getOptions()
+    for (const service in config.services) {
+      if (!options[service]) options[service] = {}
+
+      if (!(options[service].frontend in config.services[service].frontends)) {
+        options[service] = config.services[service].options // Reset settings for service
+        delete options[options[service].frontend] // Remove deprecated frontend
+      }
+
+      for (const defaultOption in config.services[service].options) {
+        if (!(defaultOption in options[service])) {
+          options[service][defaultOption] = config.services[service].options[defaultOption]
+        }
+      }
+
+      for (const frontend in config.services[service].frontends) {
+        frontends.push(frontend)
+        if (!(frontend in options) && config.services[service].frontends[frontend].instanceList) {
+          options[frontend] = defaultInstances[frontend] || []
+        }
+      }
+
+      for (const frontend of options.popupServices) {
+        if (!Object.keys(config.services).includes(frontend)) {
+          const i = options.popupServices.indexOf(frontend)
+          if (i > -1) options.popupServices.splice(i, 1)
+        }
+      }
+    }
+    const general = ["theme", "popupServices", "fetchInstances", "redirectOnlyInIncognito"]
+    const combined = [
+      ...Object.keys(config.services),
+      ...frontends,
+      ...general,
+      "exceptions",
+      "popupServices",
+      "version",
+    ]
+    for (const key in options) {
+      if (combined.indexOf(key) < 0) {
+        delete options[key] // Remove any unknown settings in options
+      }
+    }
+    browser.storage.local.set({ options }, () => {
+      resolve()
+    })
+  })
 }
 
 /**
  * @param {URL} url
  */
 async function copyRaw(url) {
-	const newUrl = await reverse(url)
-	if (newUrl) {
-		if (!isChrome) {
-			navigator.clipboard.writeText(newUrl)
-		} else {
-			var copyFrom = document.createElement("textarea");
-			copyFrom.textContent = newUrl;
-			document.body.appendChild(copyFrom);
-			copyFrom.select()
-			document.execCommand('copy')
-			copyFrom.blur();
-			document.body.removeChild(copyFrom);
-		}
-	}
+  const newUrl = await reverse(url)
+  if (newUrl) {
+    if (!isChrome) {
+      navigator.clipboard.writeText(newUrl)
+    } else {
+      var copyFrom = document.createElement("textarea")
+      copyFrom.textContent = newUrl
+      document.body.appendChild(copyFrom)
+      copyFrom.select()
+      document.execCommand("copy")
+      copyFrom.blur()
+      document.body.removeChild(copyFrom)
+    }
+  }
 }
 
 /**
  * @param {URL} url
  */
 function isException(url) {
-	if (!options.exceptions) return false
-	let exceptions = options.exceptions
-	if (exceptions && url) {
-		if (exceptions.url) {
-			for (let item of exceptions.url) {
-				item = new URL(item)
-				item = item.href.replace(/^http:\/\//, 'https://')
-				if (item == url.href) return true
-			}
-		}
-		if (exceptions.regex) for (const item of exceptions.regex) if (new RegExp(item).test(url.href)) return true
-	}
-	return false
+  if (!options.exceptions) return false
+  let exceptions = options.exceptions
+  if (exceptions && url) {
+    if (exceptions.url) {
+      for (let item of exceptions.url) {
+        item = new URL(item)
+        item = item.href.replace(/^http:\/\//, "https://")
+        if (item == url.href) return true
+      }
+    }
+    if (exceptions.regex) for (const item of exceptions.regex) if (new RegExp(item).test(url.href)) return true
+  }
+  return false
 }
 
 export default {
-	redirect,
-	redirectAsync,
-	computeService,
-	reverse,
-	initDefaults,
-	upgradeOptions,
-	processUpdate,
-	copyRaw,
-	switchInstance,
-	isException
+  redirect,
+  redirectAsync,
+  computeService,
+  reverse,
+  initDefaults,
+  upgradeOptions,
+  processUpdate,
+  copyRaw,
+  switchInstance,
+  isException,
 }
diff --git a/src/assets/javascripts/utils.js b/src/assets/javascripts/utils.js
index d28f9701..439826dd 100644
--- a/src/assets/javascripts/utils.js
+++ b/src/assets/javascripts/utils.js
@@ -1,11 +1,11 @@
 window.browser = window.browser || window.chrome
 
 /**
- * @param {Array.<T>} instances 
+ * @param {Array.<T>} instances
  * @returns {T}
  */
 function getRandomInstance(instances) {
-	return instances[~~(instances.length * Math.random())]
+  return instances[~~(instances.length * Math.random())]
 }
 
 /**
@@ -14,27 +14,28 @@ function getRandomInstance(instances) {
  * @returns {T}
  */
 function getNextInstance(currentInstanceUrl, instances) {
-	const currentInstanceIndex = instances.indexOf(currentInstanceUrl);
-	if (currentInstanceIndex === -1) return getRandomInstance(instances);
-	const nextInstanceIndex = (currentInstanceIndex + 1) % instances.length;
-	return instances[nextInstanceIndex];
+  const currentInstanceIndex = instances.indexOf(currentInstanceUrl)
+  if (currentInstanceIndex === -1) return getRandomInstance(instances)
+  const nextInstanceIndex = (currentInstanceIndex + 1) % instances.length
+  return instances[nextInstanceIndex]
 }
 
 /**
  * @param {string} str
  */
 function camelCase(str) {
-	return str.charAt(0).toUpperCase() + str.slice(1)
+  return str.charAt(0).toUpperCase() + str.slice(1)
 }
 
 /**
  * @param {URL} url
  */
 function protocolHost(url) {
-	if (url.username && url.password) return `${url.protocol}//${url.username}:${url.password}@${url.host}`
-	if (url.pathname == "/TekstoLibre/" && url.host.endsWith("github.io")) // workaround
-		return `${url.protocol}//${url.host}${url.pathname.slice(0, -1)}`
-	return `${url.protocol}//${url.host}`
+  if (url.username && url.password) return `${url.protocol}//${url.username}:${url.password}@${url.host}`
+  if (url.pathname == "/TekstoLibre/" && url.host.endsWith("github.io"))
+    // workaround
+    return `${url.protocol}//${url.host}${url.pathname.slice(0, -1)}`
+  return `${url.protocol}//${url.host}`
 }
 
 /**
@@ -59,14 +60,14 @@ function protocolHost(url) {
  * @returns {Promise<Config>}
  */
 function getConfig() {
-	return new Promise(resolve => {
-		fetch("/config.json")
-			.then(response => response.text())
-			.then(json => {
-				resolve(JSON.parse(json))
-				return
-			})
-	})
+  return new Promise(resolve => {
+    fetch("/config.json")
+      .then(response => response.text())
+      .then(json => {
+        resolve(JSON.parse(json))
+        return
+      })
+  })
 }
 
 /**
@@ -78,106 +79,108 @@ function getConfig() {
  * @returns {Promise<Object.<string, Option | string[]>>}
  */
 function getOptions() {
-	return new Promise(resolve => browser.storage.local.get("options", r => resolve(r.options)))
+  return new Promise(resolve => browser.storage.local.get("options", r => resolve(r.options)))
 }
 
 function getPingCache() {
-	return new Promise(resolve => browser.storage.local.get("pingCache", r => resolve(r.pingCache ?? {})))
+  return new Promise(resolve => browser.storage.local.get("pingCache", r => resolve(r.pingCache ?? {})))
 }
 
 function getBlacklist(options) {
-	return new Promise(resolve => {
-		let url
-		if (options.fetchInstances == 'github') url = 'https://raw.githubusercontent.com/libredirect/instances/main/blacklist.json'
-		else if (options.fetchInstances == 'codeberg') url = 'https://codeberg.org/LibRedirect/instances/raw/branch/main/blacklist.json'
-		else return resolve('disabled')
-		const http = new XMLHttpRequest()
-		http.open("GET", url, true)
-		http.onreadystatechange = () => {
-			if (http.status === 200 && http.readyState == XMLHttpRequest.DONE)
-				resolve(JSON.parse(http.responseText))
-		}
-		http.onerror = () => resolve()
-		http.ontimeout = () => resolve()
-		http.send(null)
-	})
+  return new Promise(resolve => {
+    let url
+    if (options.fetchInstances == "github")
+      url = "https://raw.githubusercontent.com/libredirect/instances/main/blacklist.json"
+    else if (options.fetchInstances == "codeberg")
+      url = "https://codeberg.org/LibRedirect/instances/raw/branch/main/blacklist.json"
+    else return resolve("disabled")
+    const http = new XMLHttpRequest()
+    http.open("GET", url, true)
+    http.onreadystatechange = () => {
+      if (http.status === 200 && http.readyState == XMLHttpRequest.DONE) resolve(JSON.parse(http.responseText))
+    }
+    http.onerror = () => resolve()
+    http.ontimeout = () => resolve()
+    http.send(null)
+  })
 }
 
 function getList(options) {
-	return new Promise(resolve => {
-		let url
-		if (options.fetchInstances == 'github') url = 'https://raw.githubusercontent.com/libredirect/instances/main/data.json'
-		else if (options.fetchInstances == 'codeberg') url = 'https://codeberg.org/LibRedirect/instances/raw/branch/main/data.json'
-		else return resolve('disabled')
-		const http = new XMLHttpRequest()
-		http.open("GET", url, true)
-		http.onreadystatechange = () => {
-			if (http.status === 200 && http.readyState == XMLHttpRequest.DONE)
-				return resolve(JSON.parse(http.responseText))
-		}
-		http.onerror = () => resolve()
-		http.ontimeout = () => resolve()
-		http.send(null)
-	})
+  return new Promise(resolve => {
+    let url
+    if (options.fetchInstances == "github")
+      url = "https://raw.githubusercontent.com/libredirect/instances/main/data.json"
+    else if (options.fetchInstances == "codeberg")
+      url = "https://codeberg.org/LibRedirect/instances/raw/branch/main/data.json"
+    else return resolve("disabled")
+    const http = new XMLHttpRequest()
+    http.open("GET", url, true)
+    http.onreadystatechange = () => {
+      if (http.status === 200 && http.readyState == XMLHttpRequest.DONE) return resolve(JSON.parse(http.responseText))
+    }
+    http.onerror = () => resolve()
+    http.ontimeout = () => resolve()
+    http.send(null)
+  })
 }
 
 /**
  * @param {string} href
  */
 function pingOnce(href) {
-	return new Promise(async resolve => {
-		let started
-		let http = new XMLHttpRequest()
-		http.timeout = 5000
-		http.ontimeout = () => resolve(5000)
-		http.onerror = () => resolve()
-		http.onreadystatechange = () => {
-			if (http.readyState == 2) {
-				if (http.status == 200) {
-					let ended = new Date().getTime()
-					http.abort()
-					resolve(ended - started)
-				} else {
-					resolve(5000 + http.status)
-				}
-			}
-		}
-		http.open("GET", `${href}?_=${new Date().getTime()}`, true)
-		started = new Date().getTime()
-		http.send(null)
-	})
+  return new Promise(async resolve => {
+    let started
+    let http = new XMLHttpRequest()
+    http.timeout = 5000
+    http.ontimeout = () => resolve(5000)
+    http.onerror = () => resolve()
+    http.onreadystatechange = () => {
+      if (http.readyState == 2) {
+        if (http.status == 200) {
+          let ended = new Date().getTime()
+          http.abort()
+          resolve(ended - started)
+        } else {
+          resolve(5000 + http.status)
+        }
+      }
+    }
+    http.open("GET", `${href}?_=${new Date().getTime()}`, true)
+    started = new Date().getTime()
+    http.send(null)
+  })
 }
 
 /**
  * @param {string} href
  */
 function ping(href) {
-	return new Promise(async resolve => {
-		let average = 0
-		let time
-		for (let i = 0; i < 3; i++) {
-			time = await pingOnce(href)
-			if (i == 0) continue
-			if (time >= 5000) {
-				resolve(time)
-				return
-			}
-			average += time
-		}
-		average = parseInt(average / 3)
-		resolve(average)
-	})
+  return new Promise(async resolve => {
+    let average = 0
+    let time
+    for (let i = 0; i < 3; i++) {
+      time = await pingOnce(href)
+      if (i == 0) continue
+      if (time >= 5000) {
+        resolve(time)
+        return
+      }
+      average += time
+    }
+    average = parseInt(average / 3)
+    resolve(average)
+  })
 }
 
 export default {
-	getRandomInstance,
-	getNextInstance,
-	protocolHost,
-	getList,
-	getBlacklist,
-	camelCase,
-	getConfig,
-	getOptions,
-	getPingCache,
-	ping,
+  getRandomInstance,
+  getNextInstance,
+  protocolHost,
+  getList,
+  getBlacklist,
+  camelCase,
+  getConfig,
+  getOptions,
+  getPingCache,
+  ping,
 }
diff --git a/src/config.json b/src/config.json
index 76e4b022..6375357f 100644
--- a/src/config.json
+++ b/src/config.json
@@ -1,1124 +1,1036 @@
 {
-	"networks": {
-		"clearnet": {
-			"tld": "org",
-			"name": "Clearnet"
-		},
-		"tor": {
-			"tld": "onion",
-			"name": "Tor"
-		},
-		"i2p": {
-			"tld": "i2p",
-			"name": "I2P"
-		},
-		"loki": {
-			"tld": "loki",
-			"name": "Lokinet"
-		}
-	},
-	"services": {
-		"youtube": {
-			"frontends": {
-				"invidious": {
-					"name": "Invidious",
-					"embeddable": true,
-					"instanceList": true,
-					"url": "https://invidious.io/"
-				},
-				"materialious": {
-					"name": "Materialious",
-					"embeddable": true,
-					"instanceList": true,
-					"url": "https://materialio.us/"
-				},
-				"piped": {
-					"excludeTargets": [
-						2,
-						3
-					],
-					"name": "Piped",
-					"embeddable": true,
-					"instanceList": true,
-					"url": "https://github.com/TeamPiped/Piped"
-				},
-				"pipedMaterial": {
-					"excludeTargets": [
-						2,
-						3
-					],
-					"name": "Piped-Material",
-					"embeddable": false,
-					"instanceList": true,
-					"url": "https://github.com/mmjee/Piped-Material"
-				},
-				"poketube": {
-					"excludeTargets": [
-						2,
-						3
-					],
-					"name": "PokeTube",
-					"embeddable": true,
-					"instanceList": true,
-					"url": "https://codeberg.org/Ashley/poketube"
-				},
-				"cloudtube": {
-					"name": "CloudTube",
-					"embeddable": false,
-					"instanceList": true,
-					"url": "https://sr.ht/~cadence/tube",
-					"excludeTargets": [
-						2,
-						3
-					]
-				},
-				"lightTube": {
-					"name": "LightTube",
-					"embeddable": false,
-					"instanceList": true,
-					"url": "https://github.com/lighttube-org/LightTube"
-				},
-				"tuboYoutube": {
-					"name": "Tubo",
-					"embeddable": false,
-					"instanceList": true,
-					"url": "https://git.migalmoreno.com/tubo/about/",
-					"excludeTargets": [
-						2,
-						3
-					]
-				},
-				"freetube": {
-					"excludeTargets": [
-						2,
-						3
-					],
-					"name": "FreeTube",
-					"embeddable": false,
-					"desktopApp": true,
-					"instanceList": false,
-					"url": "https://github.com/FreeTubeApp/FreeTube"
-				},
-				"yattee": {
-					"excludeTargets": [
-						2,
-						3
-					],
-					"name": "Yattee",
-					"embeddable": false,
-					"desktopApp": true,
-					"instanceList": false,
-					"url": "https://github.com/yattee/yattee"
-				},
-				"freetubePwa": {
-					"excludeTargets": [
-						2,
-						3
-					],
-					"name": "FreeTube PWA",
-					"embeddable": false,
-					"instanceList": false,
-					"url": "https://github.com/MarmadileManteater/FreeTubeCordova"
-				},
-				"viewtube": {
-					"name": "ViewTube",
-					"embeddable": false,
-					"instanceList": true,
-					"url": "https://github.com/ViewTube/viewtube"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.|m\\.)?youtube.com(\\/|$)(?!iframe_api\\/|redirect\\/)",
-				"^https?:\\/{2}img\\.youtube.com\\/vi\\/.*\\/..*",
-				"^https?:\\/{2}(i|s)\\.ytimg.com\\/vi\\/.*\\/..*",
-				"^https?:\\/{2}(www\\.)?youtube.com\\/watch?v=..*",
-				"^https?:\\/{2}(www\\.)?youtu\\.be\\/..*",
-				"^https?:\\/{2}(www\\.)?(youtube|youtube-nocookie)\\.com\\/embed\\/..*"
-			],
-			"name": "YouTube",
-			"options": {
-				"enabled": false,
-				"redirectType": "main_frame",
-				"frontend": "invidious",
-				"embedFrontend": "invidious",
-				"unsupportedUrls": "bypass"
-			},
-			"imageType": "png",
-			"embeddable": true,
-			"url": "https://youtube.com"
-		},
-		"youtubeMusic": {
-			"frontends": {
-				"hyperpipe": {
-					"name": "Hyperpipe",
-					"instanceList": true,
-					"url": "https://codeberg.org/Hyperpipe/Hyperpipe"
-				},
-				"invidiousMusic": {
-					"name": "Invidious",
-					"embeddable": true,
-					"instanceList": true,
-					"url": "https://invidious.io/"
-				},
-				"freetubeMusic": {
-					"name": "FreeTube",
-					"embeddable": false,
-					"desktopApp": true,
-					"instanceList": false,
-					"url": "https://github.com/FreeTubeApp/FreeTube"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}music\\.youtube\\.com\\/"
-			],
-			"name": "YT Music",
-			"options": {
-				"enabled": false,
-				"frontend": "hyperpipe",
-				"unsupportedUrls": "bypass"
-			},
-			"imageType": "png",
-			"url": "https://music.youtube.com"
-		},
-		"twitter": {
-			"frontends": {
-				"nitter": {
-					"name": "Nitter",
-					"embeddable": true,
-					"instanceList": true,
-					"url": "https://github.com/zedeus/nitter",
-					"localhost": true
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.|mobile\\.)?twitter\\.com\\/",
-				"^https?:\\/{2}(www\\.|mobile\\.)?x\\.com\\/",
-				"^https?:\\/{2}(pbs\\.|video\\.)twimg\\.com\\/",
-				"^https?:\\/{2}platform\\.x\\.com/embed\\/",
-				"^https?:\\/{2}platform\\.twitter\\.com/embed\\/",
-				"^https?:\\/{2}t\\.co\\/"
-			],
-			"name": "Twitter",
-			"options": {
-				"enabled": false,
-				"redirectType": "main_frame",
-				"unsupportedUrls": "bypass",
-				"frontend": "nitter",
-				"instance": "public"
-			},
-			"imageType": "png",
-			"embeddable": true,
-			"url": "https://twitter.com"
-		},
-		"bluesky": {
-			"frontends": {
-				"skyview": {
-					"name": "Skyview",
-					"instanceList": true,
-					"url": "https://github.com/badlogic/skyview"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}bsky\\.app\\/"
-			],
-			"name": "Bluesky",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "skyview"
-			},
-			"imageType": "svg",
-			"url": "https://bsky.app/"
-		},
-		"reddit": {
-			"frontends": {
-				"libreddit": {
-					"name": "Libreddit",
-					"instanceList": true,
-					"url": "https://github.com/spikecodes/libreddit",
-					"localhost": true
-				},
-				"redlib": {
-					"name": "Redlib",
-					"instanceList": true,
-					"url": "https://github.com/redlib-org/redlib",
-					"localhost": true
-				},
-				"teddit": {
-					"name": "Teddit",
-					"instanceList": true,
-					"url": "https://codeberg.org/teddit/teddit",
-					"localhost": true
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.|old\\.|np\\.|new\\.|amp\\.)?(reddit|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad)\\.(com|onion)(?=\\/u(ser)?\\/|\\/r\\/|\\/search|\\/new|\\/?$)",
-				"^https?:\\/{2}(i|(external-)?preview)\\.redd\\.it"
-			],
-			"name": "Reddit",
-			"options": {
-				"enabled": false,
-				"frontend": "libreddit",
-				"unsupportedUrls": "bypass",
-				"instance": "public"
-			},
-			"imageType": "png",
-			"url": "https://reddit.com"
-		},
-		"tumblr": {
-			"frontends": {
-				"priviblur": {
-					"name": "Priviblur",
-					"embeddable": true,
-					"instanceList": true,
-					"url": "https://github.com/syeopite/priviblur",
-					"localhost": true
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(media\\.|assets\\.|static\\.)?tumblr\\.com\\/",
-				"^https?:\\/{2}[0-9]+\\.media\\.tumblr\\.com\\/",
-				"^https?:\\/{2}(www\\.)?(.*)\\.tumblr.com\\/"
-			],
-			"name": "Tumblr",
-			"options": {
-				"enabled": false,
-				"redirectType": "main_frame",
-				"unsupportedUrls": "bypass",
-				"frontend": "priviblur",
-				"instance": "public"
-			},
-			"imageType": "svg",
-			"embeddable": true,
-			"url": "https://tumblr.com"
-		},
-		"twitch": {
-			"frontends": {
-				"safetwitch": {
-					"name": "SafeTwitch",
-					"embeddable": true,
-					"instanceList": true,
-					"url": "https://codeberg.org/dragongoose/safetwitch",
-					"localhost": false
-				},
-				"twineo": {
-					"name": "Twineo",
-					"embeddable": true,
-					"instanceList": true,
-					"url": "https://codeberg.org/CloudyyUw/twineo",
-					"localhost": false
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.|clips\\.)?twitch\\.(tv|com)\\/"
-			],
-			"name": "Twitch",
-			"options": {
-				"enabled": false,
-				"redirectType": "main_frame",
-				"unsupportedUrls": "bypass",
-				"frontend": "safetwitch",
-				"instance": "public"
-			},
-			"imageType": "svg",
-			"embeddable": true,
-			"url": "https://twitch.tv"
-		},
-		"tiktok": {
-			"frontends": {
-				"proxiTok": {
-					"name": "ProxiTok",
-					"instanceList": true,
-					"url": "https://github.com/pablouser1/ProxiTok",
-					"localhost": true
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?tiktok\\.com\\/"
-			],
-			"name": "TikTok",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "proxiTok",
-				"instance": "public"
-			},
-			"imageType": "png",
-			"url": "https://tiktok.com"
-		},
-		"instagram": {
-			"frontends": {
-				"proxigram": {
-					"name": "Proxigram",
-					"instanceList": true,
-					"url": "https://codeberg.org/ThePenguinDev/Proxigram",
-					"localhost": false
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?instagram\\.com"
-			],
-			"name": "Instagram",
-			"options": {
-				"enabled": false,
-				"frontend": "proxigram",
-				"unsupportedUrls": "bypass",
-				"instance": "public"
-			},
-			"imageType": "png",
-			"url": "https://www.instagram.com"
-		},
-		"imdb": {
-			"frontends": {
-				"libremdb": {
-					"name": "libremdb",
-					"instanceList": true,
-					"url": "https://github.com/zyachel/libremdb",
-					"localhost": true
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.|m\\.)?imdb\\.com"
-			],
-			"name": "IMDb",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "libremdb",
-				"instance": "public"
-			},
-			"imageType": "svg",
-			"url": "https://imdb.com"
-		},
-		"bilibili": {
-			"frontends": {
-				"mikuInvidious": {
-					"name": "MikuInvidious",
-					"instanceList": true,
-					"url": "https://0xacab.org/johnxina/mikuinvidious"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.|space\\.)?bilibili\\.com\\/",
-				"^https?:\\/{2}b23\\.tv\\/"
-			],
-			"name": "Bilibili",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "mikuInvidious"
-			},
-			"imageType": "svgMono",
-			"url": "https://bilibili.com/"
-		},
-		"pixiv": {
-			"name": "Pixiv",
-			"frontends": {
-				"pixivFe": {
-					"name": "PixivFE",
-					"instanceList": true,
-					"url": "https://codeberg.org/VnPower/pixivfe"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?pixiv\\.net\\/"
-			],
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "pixivFe"
-			},
-			"imageType": "svg",
-			"url": "https://www.pixiv.net"
-		},
-		"fandom": {
-			"frontends": {
-				"breezeWiki": {
-					"name": "BreezeWiki",
-					"instanceList": true,
-					"url": "https://breezewiki.com"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}([a-zA-Z0-9-]+\\.)?(fandom|wikia)\\.com(?=\\/wiki|\\/?$)"
-			],
-			"name": "Fandom",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"instance": "public",
-				"frontend": "breezeWiki"
-			},
-			"imageType": "svg",
-			"url": "https://fandom.com"
-		},
-		"imgur": {
-			"frontends": {
-				"rimgo": {
-					"name": "rimgo",
-					"instanceList": true,
-					"url": "https://codeberg.org/video-prize-ranch/rimgo",
-					"localhost": true,
-					"embeddable": true
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}([im]\\.)?(stack\\.)?imgur\\.(com|io)\\/"
-			],
-			"name": "Imgur",
-			"options": {
-				"enabled": false,
-				"redirectType": "main_frame",
-				"unsupportedUrls": "bypass",
-				"frontend": "rimgo",
-				"instance": "public"
-			},
-			"imageType": "png",
-			"embeddable": true,
-			"url": "https://imgur.com"
-		},
-		"pinterest": {
-			"name": "Pinterest",
-			"frontends": {
-				"binternet": {
-					"name": "Binternet",
-					"instanceList": true,
-					"url": "https://github.com/Ahwxorg/Binternet",
-					"embeddable": true
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}i\\.pinimg\\.com",
-				"^https?:\\/{2}(www\\.)?pinterest\\.com"
-			],
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"redirectType": "main_frame",
-				"frontend": "binternet"
-			},
-			"imageType": "svg",
-			"embeddable": true,
-			"url": "https://pinterest.com"
-		},
-		"soundcloud": {
-			"frontends": {
-				"tuboSoundcloud": {
-					"name": "Tubo",
-					"embeddable": false,
-					"instanceList": true,
-					"url": "https://git.migalmoreno.com/tubo/about/"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}soundcloud\\.com"
-			],
-			"name": "SoundCloud",
-			"options": {
-				"enabled": false,
-				"redirectType": "main_frame",
-				"frontend": "tuboSoundcloud",
-				"unsupportedUrls": "bypass"
-			},
-			"imageType": "svg",
-			"embeddable": false,
-			"url": "https://soundcloud.com"
-		},
-		"bandcamp": {
-			"frontends": {
-				"tent": {
-					"name": "Tent",
-					"instanceList": true,
-					"url": "https://forgejo.sny.sh/sun/Tent"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(.*\\.)?bandcamp\\.com\\/",
-				"^https?:\\/{2}(f4|t4)\\.bcbits\\.com\\/"
-			],
-			"name": "Bandcamp",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "tent"
-			},
-			"imageType": "svg",
-			"url": "https://bandcamp.com"
-		},
-		"tekstowo": {
-			"frontends": {
-				"tekstoLibre": {
-					"name": "TekstoLibre",
-					"instanceList": true,
-					"url": "https://github.com/Davilarek/TekstoLibre"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?tekstowo\\.pl\\/"
-			],
-			"name": "Tekstowo.pl",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "tekstoLibre"
-			},
-			"imageType": "svg",
-			"url": "https://www.tekstowo.pl"
-		},
-		"genius": {
-			"frontends": {
-				"dumb": {
-					"name": "Dumb",
-					"instanceList": true,
-					"url": "https://github.com/rramiachraf/dumb",
-					"localhost": true
-				},
-				"intellectual": {
-					"name": "Intellectual",
-					"instanceList": true,
-					"url": "https://github.com/Insprill/intellectual",
-					"localhost": false
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?genius\\.com\\/"
-			],
-			"name": "Genius",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "dumb",
-				"instance": "public"
-			},
-			"imageType": "svg",
-			"url": "https://genius.com"
-		},
-		"medium": {
-			"frontends": {
-				"scribe": {
-					"name": "Scribe",
-					"instanceList": true,
-					"url": "https://sr.ht/~edwardloveall/Scribe"
-				},
-				"libMedium": {
-					"name": "LibMedium",
-					"instanceList": true,
-					"url": "https://github.com/realaravinth/libmedium"
-				}
-			},
-			"targets": [
-				"^https:\\/{2}([a-zA-Z0-9-]+\\.)?medium\\.com",
-				"^https?:\\/{2}towardsdatascience\\.com\\/",
-				"^https?:\\/{2}uxdesign\\.cc\\/",
-				"^https?:\\/{2}uxplanet\\.org\\/",
-				"^https?:\\/{2}betterprogramming\\.pub\\/",
-				"^https?:\\/{2}aninjusticemag\\.com\\/",
-				"^https?:\\/{2}betterhumans\\.pub\\/",
-				"^https?:\\/{2}psiloveyou\\.xyz\\/",
-				"^https?:\\/{2}entrepreneurshandbook\\.co\\/",
-				"^https?:\\/{2}blog\\.coinbase\\.com\\/",
-				"^https?:\\/{2}levelup\\.gitconnected\\.com\\/",
-				"^https?:\\/{2}javascript\\.plainenglish\\.io\\/",
-				"^https?:\\/{2}blog\\.bitsrc\\.io\\/",
-				"^https?:\\/{2}itnext\\.io\\/",
-				"^https?:\\/{2}codeburst\\.io\\/",
-				"^https?:\\/{2}infosecwriteups\\.com\\/",
-				"^https?:\\/{2}blog\\.devgenius\\.io\\/",
-				"^https?:\\/{2}writingcooperative\\.com\\/",
-				"^https?:\\/{2}proandroiddev\\.com\\/"
-			],
-			"name": "Medium",
-			"options": {
-				"frontend": "scribe",
-				"enabled": false,
-				"unsupportedUrls": "bypass"
-			},
-			"imageType": "svg",
-			"url": "https://medium.com"
-		},
-		"quora": {
-			"frontends": {
-				"quetre": {
-					"name": "Quetre",
-					"instanceList": true,
-					"url": "https://github.com/zyachel/quetre",
-					"localhost": true
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}([a-zA-Z0-9-]+\\.)*quora\\.com\\/"
-			],
-			"name": "Quora",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "quetre",
-				"instance": "public"
-			},
-			"imageType": "png",
-			"url": "https://quora.com"
-		},
-		"github": {
-			"frontends": {
-				"gothub": {
-					"name": "Gothub",
-					"instanceList": true,
-					"url": "https://codeberg.org/gothub/gothub"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}github\\.com\\/",
-				"^https?:\\/{2}gist\\.github\\.com\\/[^\\/]+\\/[^\\/]+\\/?",
-				"^https?:\\/{2}raw\\.githubusercontent\\.com\\/[^\\/]+\\/[^\\/]+\\/?"
-			],
-			"name": "GitHub",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "gothub"
-			},
-			"imageType": "svgMono",
-			"url": "https://github.com"
-		},
-		"gitlab": {
-			"frontends": {
-				"laboratory": {
-					"name": "Laboratory",
-					"instanceList": true,
-					"url": "https://git.vitali64.duckdns.org/utils/laboratory.git/about/"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}gitlab\\.com\\/",
-				"^https?:\\/{2}gitlab\\.freedesktop\\.com\\/",
-				"^https?:\\/{2}gitlab\\.archlinux\\.com\\/"
-			],
-			"name": "Gitlab",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "laboratory"
-			},
-			"imageType": "svg",
-			"url": "https://gitlab.com/"
-		},
-		"stackOverflow": {
-			"frontends": {
-				"anonymousOverflow": {
-					"name": "AnonymousOverflow",
-					"instanceList": true,
-					"url": "https://github.com/httpjamesm/AnonymousOverflow",
-					"localhost": true
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?([a-zA-Z]+\\.)?stackoverflow\\.com\\/",
-				"(?!^https?:\\/{2}(api|data|blog)\\.)^https?:\\/{2}([a-zA-Z0-9-]+\\.)stackexchange\\.com\\/",
-				"^https?:\\/{2}(www\\.)?([a-zA-Z]+\\.)?(askubuntu\\.com|mathoverflow\\.net|serverfault\\.com|stackapps\\.com|superuser\\.com)\\/"
-			],
-			"name": "Stack Overflow",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "anonymousOverflow",
-				"instance": "public"
-			},
-			"imageType": "svgMono",
-			"url": "https://stackoverflow.com/"
-		},
-		"reuters": {
-			"frontends": {
-				"neuters": {
-					"name": "Neuters",
-					"instanceList": true,
-					"url": "https://github.com/HookedBehemoth/neuters"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?reuters\\.com\\/"
-			],
-			"name": "Reuters",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "neuters"
-			},
-			"imageType": "svg",
-			"url": "https://reuters.com"
-		},
-		"snopes": {
-			"frontends": {
-				"suds": {
-					"name": "Suds",
-					"instanceList": true,
-					"url": "https://git.vern.cc/cobra/Suds"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?snopes\\.com\\/"
-			],
-			"name": "Snopes",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "suds"
-			},
-			"imageType": "svg",
-			"url": "https://www.snopes.com"
-		},
-		"ifunny": {
-			"frontends": {
-				"unfunny": {
-					"name": "UNfunny",
-					"instanceList": true,
-					"url": "https://git.vern.cc/cobra/UNfunny"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?ifunny\\.co\\/"
-			],
-			"name": "iFunny",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "unfunny"
-			},
-			"imageType": "svg",
-			"url": "https://ifunny.co"
-		},
-		"tenor": {
-			"frontends": {
-				"soprano": {
-					"name": "Soprano",
-					"instanceList": true,
-					"url": "https://git.vern.cc/cobra/Soprano"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?tenor\\.com\\/"
-			],
-			"name": "Tenor",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "soprano"
-			},
-			"imageType": "svg",
-			"url": "https://tenor.com"
-		},
-		"knowyourmeme": {
-			"frontends": {
-				"meme": {
-					"name": "MeMe",
-					"instanceList": true,
-					"url": "https://git.vern.cc/cobra/MeMe"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?knowyourmeme\\.com\\/"
-			],
-			"name": "KnowYourMeme",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "meme"
-			},
-			"imageType": "svg",
-			"url": "https://knowyourmeme.com"
-		},
-		"urbanDictionary": {
-			"frontends": {
-				"ruralDictionary": {
-					"name": "Rural Dictionary",
-					"instanceList": true,
-					"url": "https://codeberg.org/zortazert/rural-dictionary"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?urbandictionary\\.com\\/"
-			],
-			"name": "Urban Dictionary",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "ruralDictionary"
-			},
-			"imageType": "svg",
-			"url": "https://urbandictionary.com"
-		},
-		"goodreads": {
-			"frontends": {
-				"biblioReads": {
-					"name": "BiblioReads",
-					"instanceList": true,
-					"url": "https://github.com/nesaku/BiblioReads",
-					"localhost": true
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?goodreads\\.com\\/"
-			],
-			"name": "Goodreads",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "biblioReads",
-				"instance": "public"
-			},
-			"imageType": "svgMono",
-			"url": "https://goodreads.com/"
-		},
-		"wolframAlpha": {
-			"frontends": {
-				"wolfreeAlpha": {
-					"name": "WolfreeAlpha",
-					"instanceList": true,
-					"url": "https://git.disroot.org/wolfree"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?wolframalpha\\.com\\/"
-			],
-			"name": "Wolfram Alpha",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "wolfreeAlpha"
-			},
-			"imageType": "svg",
-			"url": "https://www.wolframalpha.com"
-		},
-		"instructables": {
-			"name": "Instructables",
-			"frontends": {
-				"indestructables": {
-					"name": "Indestructables",
-					"instanceList": true,
-					"url": "https://indestructables.codeberg.page"
-				},
-				"destructables": {
-					"name": "Destructables",
-					"instanceList": true,
-					"url": "https://git.vern.cc/cobra/Destructables"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}(www\\.)?instructables\\.com\\/"
-			],
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "indestructables"
-			},
-			"imageType": "svg",
-			"url": "https://www.instructables.com"
-		},
-		"wikipedia": {
-			"frontends": {
-				"wikiless": {
-					"name": "Wikiless",
-					"instanceList": true,
-					"url": "https://wikiless.org"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}([a-z]+\\.)*wikipedia\\.org\\/?"
-			],
-			"name": "Wikipedia",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "wikiless"
-			},
-			"imageType": "svg",
-			"url": "https://wikipedia.org"
-		},
-		"waybackMachine": {
-			"frontends": {
-				"waybackClassic": {
-					"name": "Wayback Classic",
-					"instanceList": true,
-					"url": "https://github.com/ticky/wayback-classic"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}web\\.archive\\.org\\/"
-			],
-			"name": "Wayback Machine",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "waybackClassic"
-			},
-			"imageType": "svgMono",
-			"url": "https://web.archive.org"
-		},
-		"search": {
-			"frontends": {
-				"searxng": {
-					"name": "SearXNG",
-					"instanceList": true,
-					"url": "https://github.com/searxng/searxng",
-					"localhost": true
-				},
-				"searx": {
-					"name": "SearX",
-					"instanceList": true,
-					"url": "https://searx.github.io/searx/"
-				},
-				"whoogle": {
-					"name": "Whoogle",
-					"instanceList": true,
-					"url": "https://benbusby.com/projects/whoogle-search/"
-				},
-				"librey": {
-					"name": "LibreY",
-					"instanceList": true,
-					"url": "https://github.com/Ahwxorg/librey/"
-				},
-				"4get": {
-					"name": "4get",
-					"instanceList": true,
-					"url": "https://git.lolcat.ca/lolcat/4get"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}search\\.libredirect\\.invalid",
-				"^https?:\\/{2}libredirect\\.github\\.io\\/\\?q"
-			],
-			"name": "Search",
-			"options": {
-				"enabled": false,
-				"frontend": "searxng",
-				"unsupportedUrls": "bypass",
-				"instance": "public"
-			},
-			"imageType": "svgMono",
-			"url": "https://search.libredirect.invalid"
-		},
-		"translate": {
-			"frontends": {
-				"simplyTranslate": {
-					"name": "SimplyTranslate",
-					"instanceList": true,
-					"url": "https://git.sr.ht/~metalune/simplytranslate_web",
-					"localhost": true
-				},
-				"mozhi": {
-					"name": "Mozhi",
-					"instanceList": true,
-					"url": "https://codeberg.org/aryak/mozhi",
-					"localhost": false
-				},
-				"libreTranslate": {
-					"name": "LibreTranslate",
-					"instanceList": true,
-					"url": "https://github.com/LibreTranslate/LibreTranslate"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}translate\\.google(\\.[a-z]{2,3}){1,2}\\/",
-				"^https?:\\/{2}translate\\.libredirect\\.invalid"
-			],
-			"name": "Translate",
-			"options": {
-				"enabled": false,
-				"frontend": "simplyTranslate",
-				"unsupportedUrls": "bypass",
-				"instance": "public"
-			},
-			"imageType": "svgMono",
-			"url": "https://translate.libredirect.invalid"
-		},
-		"maps": {
-			"frontends": {
-				"osm": {
-					"name": "OpenStreetMap",
-					"instanceList": true,
-					"embeddable": true,
-					"url": "https://www.openstreetmap.org/"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}maps\\.libredirect\\.invalid",
-				"^https?:\\/{2}(((www|maps)\\.)?(google\\.).*(\\/maps)|maps\\.(google\\.).*)"
-			],
-			"name": "Maps",
-			"options": {
-				"redirectType": "main_frame",
-				"enabled": false,
-				"frontend": "osm",
-				"unsupportedUrls": "bypass"
-			},
-			"imageType": "svgMono",
-			"url": "https://maps.libredirect.invalid"
-		},
-		"meet": {
-			"name": "Meet",
-			"frontends": {
-				"jitsi": {
-					"name": "Jitsi",
-					"instanceList": true,
-					"url": "https://jitsi.org"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}meet\\.libredirect\\.invalid\\/"
-			],
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "jitsi"
-			},
-			"imageType": "svgMono",
-			"url": "https://meet.libredirect.invalid"
-		},
-		"sendFiles": {
-			"frontends": {
-				"send": {
-					"name": "Send",
-					"instanceList": true,
-					"url": "https://gitlab.com/timvisee/send"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}send\\.libredirect\\.invalid",
-				"^https?:\\/{2}send\\.firefox\\.com\\/?$",
-				"^https?:\\/{2}sendfiles\\.online\\/?$"
-			],
-			"name": "Send Files",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "send"
-			},
-			"imageType": "svgMono",
-			"url": "https://send.libredirect.invalid"
-		},
-		"textStorage": {
-			"frontends": {
-				"privateBin": {
-					"name": "PrivateBin",
-					"instanceList": true,
-					"url": "https://privatebin.info"
-				},
-				"pasted": {
-					"name": "Pasted",
-					"instanceList": true,
-					"url": "https://github.com/Dragynfruit/pasted"
-				}
-			},
-			"targets": [
-				"^https?:\\/{2}paste\\.libredirect\\.invalid"
-			],
-			"name": "Paste Text",
-			"options": {
-				"enabled": false,
-				"unsupportedUrls": "bypass",
-				"frontend": "privateBin"
-			},
-			"imageType": "svgMono",
-			"url": "https://paste.libredirect.invalid"
-		}
-	}
-}
\ No newline at end of file
+  "networks": {
+    "clearnet": {
+      "tld": "org",
+      "name": "Clearnet"
+    },
+    "tor": {
+      "tld": "onion",
+      "name": "Tor"
+    },
+    "i2p": {
+      "tld": "i2p",
+      "name": "I2P"
+    },
+    "loki": {
+      "tld": "loki",
+      "name": "Lokinet"
+    }
+  },
+  "services": {
+    "youtube": {
+      "frontends": {
+        "invidious": {
+          "name": "Invidious",
+          "embeddable": true,
+          "instanceList": true,
+          "url": "https://invidious.io/"
+        },
+        "materialious": {
+          "name": "Materialious",
+          "embeddable": true,
+          "instanceList": true,
+          "url": "https://materialio.us/"
+        },
+        "piped": {
+          "excludeTargets": [2, 3],
+          "name": "Piped",
+          "embeddable": true,
+          "instanceList": true,
+          "url": "https://github.com/TeamPiped/Piped"
+        },
+        "pipedMaterial": {
+          "excludeTargets": [2, 3],
+          "name": "Piped-Material",
+          "embeddable": false,
+          "instanceList": true,
+          "url": "https://github.com/mmjee/Piped-Material"
+        },
+        "poketube": {
+          "excludeTargets": [2, 3],
+          "name": "PokeTube",
+          "embeddable": true,
+          "instanceList": true,
+          "url": "https://codeberg.org/Ashley/poketube"
+        },
+        "cloudtube": {
+          "name": "CloudTube",
+          "embeddable": false,
+          "instanceList": true,
+          "url": "https://sr.ht/~cadence/tube",
+          "excludeTargets": [2, 3]
+        },
+        "lightTube": {
+          "name": "LightTube",
+          "embeddable": false,
+          "instanceList": true,
+          "url": "https://github.com/lighttube-org/LightTube"
+        },
+        "tuboYoutube": {
+          "name": "Tubo",
+          "embeddable": false,
+          "instanceList": true,
+          "url": "https://git.migalmoreno.com/tubo/about/",
+          "excludeTargets": [2, 3]
+        },
+        "freetube": {
+          "excludeTargets": [2, 3],
+          "name": "FreeTube",
+          "embeddable": false,
+          "desktopApp": true,
+          "instanceList": false,
+          "url": "https://github.com/FreeTubeApp/FreeTube"
+        },
+        "yattee": {
+          "excludeTargets": [2, 3],
+          "name": "Yattee",
+          "embeddable": false,
+          "desktopApp": true,
+          "instanceList": false,
+          "url": "https://github.com/yattee/yattee"
+        },
+        "freetubePwa": {
+          "excludeTargets": [2, 3],
+          "name": "FreeTube PWA",
+          "embeddable": false,
+          "instanceList": false,
+          "url": "https://github.com/MarmadileManteater/FreeTubeCordova"
+        },
+        "viewtube": {
+          "name": "ViewTube",
+          "embeddable": false,
+          "instanceList": true,
+          "url": "https://github.com/ViewTube/viewtube"
+        }
+      },
+      "targets": [
+        "^https?:\\/{2}(www\\.|m\\.)?youtube.com(\\/|$)(?!iframe_api\\/|redirect\\/)",
+        "^https?:\\/{2}img\\.youtube.com\\/vi\\/.*\\/..*",
+        "^https?:\\/{2}(i|s)\\.ytimg.com\\/vi\\/.*\\/..*",
+        "^https?:\\/{2}(www\\.)?youtube.com\\/watch?v=..*",
+        "^https?:\\/{2}(www\\.)?youtu\\.be\\/..*",
+        "^https?:\\/{2}(www\\.)?(youtube|youtube-nocookie)\\.com\\/embed\\/..*"
+      ],
+      "name": "YouTube",
+      "options": {
+        "enabled": false,
+        "redirectType": "main_frame",
+        "frontend": "invidious",
+        "embedFrontend": "invidious",
+        "unsupportedUrls": "bypass"
+      },
+      "imageType": "png",
+      "embeddable": true,
+      "url": "https://youtube.com"
+    },
+    "youtubeMusic": {
+      "frontends": {
+        "hyperpipe": {
+          "name": "Hyperpipe",
+          "instanceList": true,
+          "url": "https://codeberg.org/Hyperpipe/Hyperpipe"
+        },
+        "invidiousMusic": {
+          "name": "Invidious",
+          "embeddable": true,
+          "instanceList": true,
+          "url": "https://invidious.io/"
+        },
+        "freetubeMusic": {
+          "name": "FreeTube",
+          "embeddable": false,
+          "desktopApp": true,
+          "instanceList": false,
+          "url": "https://github.com/FreeTubeApp/FreeTube"
+        }
+      },
+      "targets": ["^https?:\\/{2}music\\.youtube\\.com\\/"],
+      "name": "YT Music",
+      "options": {
+        "enabled": false,
+        "frontend": "hyperpipe",
+        "unsupportedUrls": "bypass"
+      },
+      "imageType": "png",
+      "url": "https://music.youtube.com"
+    },
+    "twitter": {
+      "frontends": {
+        "nitter": {
+          "name": "Nitter",
+          "embeddable": true,
+          "instanceList": true,
+          "url": "https://github.com/zedeus/nitter",
+          "localhost": true
+        }
+      },
+      "targets": [
+        "^https?:\\/{2}(www\\.|mobile\\.)?twitter\\.com\\/",
+        "^https?:\\/{2}(www\\.|mobile\\.)?x\\.com\\/",
+        "^https?:\\/{2}(pbs\\.|video\\.)twimg\\.com\\/",
+        "^https?:\\/{2}platform\\.x\\.com/embed\\/",
+        "^https?:\\/{2}platform\\.twitter\\.com/embed\\/",
+        "^https?:\\/{2}t\\.co\\/"
+      ],
+      "name": "Twitter",
+      "options": {
+        "enabled": false,
+        "redirectType": "main_frame",
+        "unsupportedUrls": "bypass",
+        "frontend": "nitter",
+        "instance": "public"
+      },
+      "imageType": "png",
+      "embeddable": true,
+      "url": "https://twitter.com"
+    },
+    "bluesky": {
+      "frontends": {
+        "skyview": {
+          "name": "Skyview",
+          "instanceList": true,
+          "url": "https://github.com/badlogic/skyview"
+        }
+      },
+      "targets": ["^https?:\\/{2}bsky\\.app\\/"],
+      "name": "Bluesky",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "skyview"
+      },
+      "imageType": "svg",
+      "url": "https://bsky.app/"
+    },
+    "reddit": {
+      "frontends": {
+        "libreddit": {
+          "name": "Libreddit",
+          "instanceList": true,
+          "url": "https://github.com/spikecodes/libreddit",
+          "localhost": true
+        },
+        "redlib": {
+          "name": "Redlib",
+          "instanceList": true,
+          "url": "https://github.com/redlib-org/redlib",
+          "localhost": true
+        },
+        "teddit": {
+          "name": "Teddit",
+          "instanceList": true,
+          "url": "https://codeberg.org/teddit/teddit",
+          "localhost": true
+        }
+      },
+      "targets": [
+        "^https?:\\/{2}(www\\.|old\\.|np\\.|new\\.|amp\\.)?(reddit|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad)\\.(com|onion)(?=\\/u(ser)?\\/|\\/r\\/|\\/search|\\/new|\\/?$)",
+        "^https?:\\/{2}(i|(external-)?preview)\\.redd\\.it"
+      ],
+      "name": "Reddit",
+      "options": {
+        "enabled": false,
+        "frontend": "libreddit",
+        "unsupportedUrls": "bypass",
+        "instance": "public"
+      },
+      "imageType": "png",
+      "url": "https://reddit.com"
+    },
+    "tumblr": {
+      "frontends": {
+        "priviblur": {
+          "name": "Priviblur",
+          "embeddable": true,
+          "instanceList": true,
+          "url": "https://github.com/syeopite/priviblur",
+          "localhost": true
+        }
+      },
+      "targets": [
+        "^https?:\\/{2}(media\\.|assets\\.|static\\.)?tumblr\\.com\\/",
+        "^https?:\\/{2}[0-9]+\\.media\\.tumblr\\.com\\/",
+        "^https?:\\/{2}(www\\.)?(.*)\\.tumblr.com\\/"
+      ],
+      "name": "Tumblr",
+      "options": {
+        "enabled": false,
+        "redirectType": "main_frame",
+        "unsupportedUrls": "bypass",
+        "frontend": "priviblur",
+        "instance": "public"
+      },
+      "imageType": "svg",
+      "embeddable": true,
+      "url": "https://tumblr.com"
+    },
+    "twitch": {
+      "frontends": {
+        "safetwitch": {
+          "name": "SafeTwitch",
+          "embeddable": true,
+          "instanceList": true,
+          "url": "https://codeberg.org/dragongoose/safetwitch",
+          "localhost": false
+        },
+        "twineo": {
+          "name": "Twineo",
+          "embeddable": true,
+          "instanceList": true,
+          "url": "https://codeberg.org/CloudyyUw/twineo",
+          "localhost": false
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.|clips\\.)?twitch\\.(tv|com)\\/"],
+      "name": "Twitch",
+      "options": {
+        "enabled": false,
+        "redirectType": "main_frame",
+        "unsupportedUrls": "bypass",
+        "frontend": "safetwitch",
+        "instance": "public"
+      },
+      "imageType": "svg",
+      "embeddable": true,
+      "url": "https://twitch.tv"
+    },
+    "tiktok": {
+      "frontends": {
+        "proxiTok": {
+          "name": "ProxiTok",
+          "instanceList": true,
+          "url": "https://github.com/pablouser1/ProxiTok",
+          "localhost": true
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?tiktok\\.com\\/"],
+      "name": "TikTok",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "proxiTok",
+        "instance": "public"
+      },
+      "imageType": "png",
+      "url": "https://tiktok.com"
+    },
+    "instagram": {
+      "frontends": {
+        "proxigram": {
+          "name": "Proxigram",
+          "instanceList": true,
+          "url": "https://codeberg.org/ThePenguinDev/Proxigram",
+          "localhost": false
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?instagram\\.com"],
+      "name": "Instagram",
+      "options": {
+        "enabled": false,
+        "frontend": "proxigram",
+        "unsupportedUrls": "bypass",
+        "instance": "public"
+      },
+      "imageType": "png",
+      "url": "https://www.instagram.com"
+    },
+    "imdb": {
+      "frontends": {
+        "libremdb": {
+          "name": "libremdb",
+          "instanceList": true,
+          "url": "https://github.com/zyachel/libremdb",
+          "localhost": true
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.|m\\.)?imdb\\.com"],
+      "name": "IMDb",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "libremdb",
+        "instance": "public"
+      },
+      "imageType": "svg",
+      "url": "https://imdb.com"
+    },
+    "bilibili": {
+      "frontends": {
+        "mikuInvidious": {
+          "name": "MikuInvidious",
+          "instanceList": true,
+          "url": "https://0xacab.org/johnxina/mikuinvidious"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.|space\\.)?bilibili\\.com\\/", "^https?:\\/{2}b23\\.tv\\/"],
+      "name": "Bilibili",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "mikuInvidious"
+      },
+      "imageType": "svgMono",
+      "url": "https://bilibili.com/"
+    },
+    "pixiv": {
+      "name": "Pixiv",
+      "frontends": {
+        "pixivFe": {
+          "name": "PixivFE",
+          "instanceList": true,
+          "url": "https://codeberg.org/VnPower/pixivfe"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?pixiv\\.net\\/"],
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "pixivFe"
+      },
+      "imageType": "svg",
+      "url": "https://www.pixiv.net"
+    },
+    "fandom": {
+      "frontends": {
+        "breezeWiki": {
+          "name": "BreezeWiki",
+          "instanceList": true,
+          "url": "https://breezewiki.com"
+        }
+      },
+      "targets": ["^https?:\\/{2}([a-zA-Z0-9-]+\\.)?(fandom|wikia)\\.com(?=\\/wiki|\\/?$)"],
+      "name": "Fandom",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "instance": "public",
+        "frontend": "breezeWiki"
+      },
+      "imageType": "svg",
+      "url": "https://fandom.com"
+    },
+    "imgur": {
+      "frontends": {
+        "rimgo": {
+          "name": "rimgo",
+          "instanceList": true,
+          "url": "https://codeberg.org/video-prize-ranch/rimgo",
+          "localhost": true,
+          "embeddable": true
+        }
+      },
+      "targets": ["^https?:\\/{2}([im]\\.)?(stack\\.)?imgur\\.(com|io)\\/"],
+      "name": "Imgur",
+      "options": {
+        "enabled": false,
+        "redirectType": "main_frame",
+        "unsupportedUrls": "bypass",
+        "frontend": "rimgo",
+        "instance": "public"
+      },
+      "imageType": "png",
+      "embeddable": true,
+      "url": "https://imgur.com"
+    },
+    "pinterest": {
+      "name": "Pinterest",
+      "frontends": {
+        "binternet": {
+          "name": "Binternet",
+          "instanceList": true,
+          "url": "https://github.com/Ahwxorg/Binternet",
+          "embeddable": true
+        }
+      },
+      "targets": ["^https?:\\/{2}i\\.pinimg\\.com", "^https?:\\/{2}(www\\.)?pinterest\\.com"],
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "redirectType": "main_frame",
+        "frontend": "binternet"
+      },
+      "imageType": "svg",
+      "embeddable": true,
+      "url": "https://pinterest.com"
+    },
+    "soundcloud": {
+      "frontends": {
+        "tuboSoundcloud": {
+          "name": "Tubo",
+          "embeddable": false,
+          "instanceList": true,
+          "url": "https://git.migalmoreno.com/tubo/about/"
+        }
+      },
+      "targets": ["^https?:\\/{2}soundcloud\\.com"],
+      "name": "SoundCloud",
+      "options": {
+        "enabled": false,
+        "redirectType": "main_frame",
+        "frontend": "tuboSoundcloud",
+        "unsupportedUrls": "bypass"
+      },
+      "imageType": "svg",
+      "embeddable": false,
+      "url": "https://soundcloud.com"
+    },
+    "bandcamp": {
+      "frontends": {
+        "tent": {
+          "name": "Tent",
+          "instanceList": true,
+          "url": "https://forgejo.sny.sh/sun/Tent"
+        }
+      },
+      "targets": ["^https?:\\/{2}(.*\\.)?bandcamp\\.com\\/", "^https?:\\/{2}(f4|t4)\\.bcbits\\.com\\/"],
+      "name": "Bandcamp",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "tent"
+      },
+      "imageType": "svg",
+      "url": "https://bandcamp.com"
+    },
+    "tekstowo": {
+      "frontends": {
+        "tekstoLibre": {
+          "name": "TekstoLibre",
+          "instanceList": true,
+          "url": "https://github.com/Davilarek/TekstoLibre"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?tekstowo\\.pl\\/"],
+      "name": "Tekstowo.pl",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "tekstoLibre"
+      },
+      "imageType": "svg",
+      "url": "https://www.tekstowo.pl"
+    },
+    "genius": {
+      "frontends": {
+        "dumb": {
+          "name": "Dumb",
+          "instanceList": true,
+          "url": "https://github.com/rramiachraf/dumb",
+          "localhost": true
+        },
+        "intellectual": {
+          "name": "Intellectual",
+          "instanceList": true,
+          "url": "https://github.com/Insprill/intellectual",
+          "localhost": false
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?genius\\.com\\/"],
+      "name": "Genius",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "dumb",
+        "instance": "public"
+      },
+      "imageType": "svg",
+      "url": "https://genius.com"
+    },
+    "medium": {
+      "frontends": {
+        "scribe": {
+          "name": "Scribe",
+          "instanceList": true,
+          "url": "https://sr.ht/~edwardloveall/Scribe"
+        },
+        "libMedium": {
+          "name": "LibMedium",
+          "instanceList": true,
+          "url": "https://github.com/realaravinth/libmedium"
+        }
+      },
+      "targets": [
+        "^https:\\/{2}([a-zA-Z0-9-]+\\.)?medium\\.com",
+        "^https?:\\/{2}towardsdatascience\\.com\\/",
+        "^https?:\\/{2}uxdesign\\.cc\\/",
+        "^https?:\\/{2}uxplanet\\.org\\/",
+        "^https?:\\/{2}betterprogramming\\.pub\\/",
+        "^https?:\\/{2}aninjusticemag\\.com\\/",
+        "^https?:\\/{2}betterhumans\\.pub\\/",
+        "^https?:\\/{2}psiloveyou\\.xyz\\/",
+        "^https?:\\/{2}entrepreneurshandbook\\.co\\/",
+        "^https?:\\/{2}blog\\.coinbase\\.com\\/",
+        "^https?:\\/{2}levelup\\.gitconnected\\.com\\/",
+        "^https?:\\/{2}javascript\\.plainenglish\\.io\\/",
+        "^https?:\\/{2}blog\\.bitsrc\\.io\\/",
+        "^https?:\\/{2}itnext\\.io\\/",
+        "^https?:\\/{2}codeburst\\.io\\/",
+        "^https?:\\/{2}infosecwriteups\\.com\\/",
+        "^https?:\\/{2}blog\\.devgenius\\.io\\/",
+        "^https?:\\/{2}writingcooperative\\.com\\/",
+        "^https?:\\/{2}proandroiddev\\.com\\/"
+      ],
+      "name": "Medium",
+      "options": {
+        "frontend": "scribe",
+        "enabled": false,
+        "unsupportedUrls": "bypass"
+      },
+      "imageType": "svg",
+      "url": "https://medium.com"
+    },
+    "quora": {
+      "frontends": {
+        "quetre": {
+          "name": "Quetre",
+          "instanceList": true,
+          "url": "https://github.com/zyachel/quetre",
+          "localhost": true
+        }
+      },
+      "targets": ["^https?:\\/{2}([a-zA-Z0-9-]+\\.)*quora\\.com\\/"],
+      "name": "Quora",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "quetre",
+        "instance": "public"
+      },
+      "imageType": "png",
+      "url": "https://quora.com"
+    },
+    "github": {
+      "frontends": {
+        "gothub": {
+          "name": "Gothub",
+          "instanceList": true,
+          "url": "https://codeberg.org/gothub/gothub"
+        }
+      },
+      "targets": [
+        "^https?:\\/{2}github\\.com\\/",
+        "^https?:\\/{2}gist\\.github\\.com\\/[^\\/]+\\/[^\\/]+\\/?",
+        "^https?:\\/{2}raw\\.githubusercontent\\.com\\/[^\\/]+\\/[^\\/]+\\/?"
+      ],
+      "name": "GitHub",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "gothub"
+      },
+      "imageType": "svgMono",
+      "url": "https://github.com"
+    },
+    "gitlab": {
+      "frontends": {
+        "laboratory": {
+          "name": "Laboratory",
+          "instanceList": true,
+          "url": "https://git.vitali64.duckdns.org/utils/laboratory.git/about/"
+        }
+      },
+      "targets": [
+        "^https?:\\/{2}gitlab\\.com\\/",
+        "^https?:\\/{2}gitlab\\.freedesktop\\.com\\/",
+        "^https?:\\/{2}gitlab\\.archlinux\\.com\\/"
+      ],
+      "name": "Gitlab",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "laboratory"
+      },
+      "imageType": "svg",
+      "url": "https://gitlab.com/"
+    },
+    "stackOverflow": {
+      "frontends": {
+        "anonymousOverflow": {
+          "name": "AnonymousOverflow",
+          "instanceList": true,
+          "url": "https://github.com/httpjamesm/AnonymousOverflow",
+          "localhost": true
+        }
+      },
+      "targets": [
+        "^https?:\\/{2}(www\\.)?([a-zA-Z]+\\.)?stackoverflow\\.com\\/",
+        "(?!^https?:\\/{2}(api|data|blog)\\.)^https?:\\/{2}([a-zA-Z0-9-]+\\.)stackexchange\\.com\\/",
+        "^https?:\\/{2}(www\\.)?([a-zA-Z]+\\.)?(askubuntu\\.com|mathoverflow\\.net|serverfault\\.com|stackapps\\.com|superuser\\.com)\\/"
+      ],
+      "name": "Stack Overflow",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "anonymousOverflow",
+        "instance": "public"
+      },
+      "imageType": "svgMono",
+      "url": "https://stackoverflow.com/"
+    },
+    "reuters": {
+      "frontends": {
+        "neuters": {
+          "name": "Neuters",
+          "instanceList": true,
+          "url": "https://github.com/HookedBehemoth/neuters"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?reuters\\.com\\/"],
+      "name": "Reuters",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "neuters"
+      },
+      "imageType": "svg",
+      "url": "https://reuters.com"
+    },
+    "snopes": {
+      "frontends": {
+        "suds": {
+          "name": "Suds",
+          "instanceList": true,
+          "url": "https://git.vern.cc/cobra/Suds"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?snopes\\.com\\/"],
+      "name": "Snopes",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "suds"
+      },
+      "imageType": "svg",
+      "url": "https://www.snopes.com"
+    },
+    "ifunny": {
+      "frontends": {
+        "unfunny": {
+          "name": "UNfunny",
+          "instanceList": true,
+          "url": "https://git.vern.cc/cobra/UNfunny"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?ifunny\\.co\\/"],
+      "name": "iFunny",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "unfunny"
+      },
+      "imageType": "svg",
+      "url": "https://ifunny.co"
+    },
+    "tenor": {
+      "frontends": {
+        "soprano": {
+          "name": "Soprano",
+          "instanceList": true,
+          "url": "https://git.vern.cc/cobra/Soprano"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?tenor\\.com\\/"],
+      "name": "Tenor",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "soprano"
+      },
+      "imageType": "svg",
+      "url": "https://tenor.com"
+    },
+    "knowyourmeme": {
+      "frontends": {
+        "meme": {
+          "name": "MeMe",
+          "instanceList": true,
+          "url": "https://git.vern.cc/cobra/MeMe"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?knowyourmeme\\.com\\/"],
+      "name": "KnowYourMeme",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "meme"
+      },
+      "imageType": "svg",
+      "url": "https://knowyourmeme.com"
+    },
+    "urbanDictionary": {
+      "frontends": {
+        "ruralDictionary": {
+          "name": "Rural Dictionary",
+          "instanceList": true,
+          "url": "https://codeberg.org/zortazert/rural-dictionary"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?urbandictionary\\.com\\/"],
+      "name": "Urban Dictionary",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "ruralDictionary"
+      },
+      "imageType": "svg",
+      "url": "https://urbandictionary.com"
+    },
+    "goodreads": {
+      "frontends": {
+        "biblioReads": {
+          "name": "BiblioReads",
+          "instanceList": true,
+          "url": "https://github.com/nesaku/BiblioReads",
+          "localhost": true
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?goodreads\\.com\\/"],
+      "name": "Goodreads",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "biblioReads",
+        "instance": "public"
+      },
+      "imageType": "svgMono",
+      "url": "https://goodreads.com/"
+    },
+    "wolframAlpha": {
+      "frontends": {
+        "wolfreeAlpha": {
+          "name": "WolfreeAlpha",
+          "instanceList": true,
+          "url": "https://git.disroot.org/wolfree"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?wolframalpha\\.com\\/"],
+      "name": "Wolfram Alpha",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "wolfreeAlpha"
+      },
+      "imageType": "svg",
+      "url": "https://www.wolframalpha.com"
+    },
+    "instructables": {
+      "name": "Instructables",
+      "frontends": {
+        "indestructables": {
+          "name": "Indestructables",
+          "instanceList": true,
+          "url": "https://indestructables.codeberg.page"
+        },
+        "destructables": {
+          "name": "Destructables",
+          "instanceList": true,
+          "url": "https://git.vern.cc/cobra/Destructables"
+        }
+      },
+      "targets": ["^https?:\\/{2}(www\\.)?instructables\\.com\\/"],
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "indestructables"
+      },
+      "imageType": "svg",
+      "url": "https://www.instructables.com"
+    },
+    "wikipedia": {
+      "frontends": {
+        "wikiless": {
+          "name": "Wikiless",
+          "instanceList": true,
+          "url": "https://wikiless.org"
+        }
+      },
+      "targets": ["^https?:\\/{2}([a-z]+\\.)*wikipedia\\.org\\/?"],
+      "name": "Wikipedia",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "wikiless"
+      },
+      "imageType": "svg",
+      "url": "https://wikipedia.org"
+    },
+    "waybackMachine": {
+      "frontends": {
+        "waybackClassic": {
+          "name": "Wayback Classic",
+          "instanceList": true,
+          "url": "https://github.com/ticky/wayback-classic"
+        }
+      },
+      "targets": ["^https?:\\/{2}web\\.archive\\.org\\/"],
+      "name": "Wayback Machine",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "waybackClassic"
+      },
+      "imageType": "svgMono",
+      "url": "https://web.archive.org"
+    },
+    "search": {
+      "frontends": {
+        "searxng": {
+          "name": "SearXNG",
+          "instanceList": true,
+          "url": "https://github.com/searxng/searxng",
+          "localhost": true
+        },
+        "searx": {
+          "name": "SearX",
+          "instanceList": true,
+          "url": "https://searx.github.io/searx/"
+        },
+        "whoogle": {
+          "name": "Whoogle",
+          "instanceList": true,
+          "url": "https://benbusby.com/projects/whoogle-search/"
+        },
+        "librey": {
+          "name": "LibreY",
+          "instanceList": true,
+          "url": "https://github.com/Ahwxorg/librey/"
+        },
+        "4get": {
+          "name": "4get",
+          "instanceList": true,
+          "url": "https://git.lolcat.ca/lolcat/4get"
+        }
+      },
+      "targets": ["^https?:\\/{2}search\\.libredirect\\.invalid", "^https?:\\/{2}libredirect\\.github\\.io\\/\\?q"],
+      "name": "Search",
+      "options": {
+        "enabled": false,
+        "frontend": "searxng",
+        "unsupportedUrls": "bypass",
+        "instance": "public"
+      },
+      "imageType": "svgMono",
+      "url": "https://search.libredirect.invalid"
+    },
+    "translate": {
+      "frontends": {
+        "simplyTranslate": {
+          "name": "SimplyTranslate",
+          "instanceList": true,
+          "url": "https://git.sr.ht/~metalune/simplytranslate_web",
+          "localhost": true
+        },
+        "mozhi": {
+          "name": "Mozhi",
+          "instanceList": true,
+          "url": "https://codeberg.org/aryak/mozhi",
+          "localhost": false
+        },
+        "libreTranslate": {
+          "name": "LibreTranslate",
+          "instanceList": true,
+          "url": "https://github.com/LibreTranslate/LibreTranslate"
+        }
+      },
+      "targets": [
+        "^https?:\\/{2}translate\\.google(\\.[a-z]{2,3}){1,2}\\/",
+        "^https?:\\/{2}translate\\.libredirect\\.invalid"
+      ],
+      "name": "Translate",
+      "options": {
+        "enabled": false,
+        "frontend": "simplyTranslate",
+        "unsupportedUrls": "bypass",
+        "instance": "public"
+      },
+      "imageType": "svgMono",
+      "url": "https://translate.libredirect.invalid"
+    },
+    "maps": {
+      "frontends": {
+        "osm": {
+          "name": "OpenStreetMap",
+          "instanceList": true,
+          "embeddable": true,
+          "url": "https://www.openstreetmap.org/"
+        }
+      },
+      "targets": [
+        "^https?:\\/{2}maps\\.libredirect\\.invalid",
+        "^https?:\\/{2}(((www|maps)\\.)?(google\\.).*(\\/maps)|maps\\.(google\\.).*)"
+      ],
+      "name": "Maps",
+      "options": {
+        "redirectType": "main_frame",
+        "enabled": false,
+        "frontend": "osm",
+        "unsupportedUrls": "bypass"
+      },
+      "imageType": "svgMono",
+      "url": "https://maps.libredirect.invalid"
+    },
+    "meet": {
+      "name": "Meet",
+      "frontends": {
+        "jitsi": {
+          "name": "Jitsi",
+          "instanceList": true,
+          "url": "https://jitsi.org"
+        }
+      },
+      "targets": ["^https?:\\/{2}meet\\.libredirect\\.invalid\\/"],
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "jitsi"
+      },
+      "imageType": "svgMono",
+      "url": "https://meet.libredirect.invalid"
+    },
+    "sendFiles": {
+      "frontends": {
+        "send": {
+          "name": "Send",
+          "instanceList": true,
+          "url": "https://gitlab.com/timvisee/send"
+        }
+      },
+      "targets": [
+        "^https?:\\/{2}send\\.libredirect\\.invalid",
+        "^https?:\\/{2}send\\.firefox\\.com\\/?$",
+        "^https?:\\/{2}sendfiles\\.online\\/?$"
+      ],
+      "name": "Send Files",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "send"
+      },
+      "imageType": "svgMono",
+      "url": "https://send.libredirect.invalid"
+    },
+    "textStorage": {
+      "frontends": {
+        "privateBin": {
+          "name": "PrivateBin",
+          "instanceList": true,
+          "url": "https://privatebin.info"
+        },
+        "pasted": {
+          "name": "Pasted",
+          "instanceList": true,
+          "url": "https://github.com/Dragynfruit/pasted"
+        }
+      },
+      "targets": ["^https?:\\/{2}paste\\.libredirect\\.invalid"],
+      "name": "Paste Text",
+      "options": {
+        "enabled": false,
+        "unsupportedUrls": "bypass",
+        "frontend": "privateBin"
+      },
+      "imageType": "svgMono",
+      "url": "https://paste.libredirect.invalid"
+    }
+  }
+}
diff --git a/src/manifest.json b/src/manifest.json
index cab3181b..cf763247 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -1,91 +1,82 @@
 {
-	"name": "__MSG_extensionName__",
-	"description": "__MSG_extensionDescription__",
-	"version": "2.8.5",
-	"manifest_version": 2,
-	"browser_specific_settings": {
-		"gecko": {
-			"id": "7esoorv3@alefvanoon.anonaddy.me",
-			"strict_min_version": "89.0"
-		},
-		"gecko_android": {
-			"strict_min_version": "113.0"
-		}
-	},
-	"background": {
-		"page": "pages/background/background.html",
-		"persistent": true
-	},
-	"icons": {
-		"16": "assets/images/libredirect-16.png",
-		"32": "assets/images/libredirect-32.png",
-		"48": "assets/images/libredirect-48.png",
-		"128": "assets/images/libredirect-128.png"
-	},
-	"permissions": [
-		"webRequest",
-		"webRequestBlocking",
-		"storage",
-		"clipboardWrite",
-		"contextMenus",
-		"<all_urls>"
-	],
-	"optional_permissions": [
-		"bookmarks"
-	],
-	"browser_action": {
-		"default_title": "__MSG_extensionName__",
-		"browser_style": false,
-		"default_popup": "pages/popup/popup.html",
-		"default_icon": {
-			"16": "assets/images/libredirect-16.png",
-			"32": "assets/images/libredirect-32.png",
-			"48": "assets/images/libredirect-48.png",
-			"128": "assets/images/libredirect-128.png"
-		}
-	},
-	"options_ui": {
-		"page": "pages/options/index.html",
-		"browser_style": false,
-		"open_in_tab": true
-	},
-	"chrome_settings_overrides": {
-		"search_provider": {
-			"name": "__MSG_extensionName__",
-			"keyword": "@libredirect",
-			"favicon_url": "https://raw.githubusercontent.com/libredirect/libredirect/master/src/assets/images/libredirect-16.png",
-			"search_url": "https://search.libredirect.invalid/?q={searchTerms}",
-			"encoding": "UTF-8",
-			"is_default": false
-		}
-	},
-	"commands": {
-		"switchInstance": {
-			"suggested_key": {
-				"default": "Alt+Shift+L"
-			},
-			"description": "__MSG_switchInstance__"
-		},
-		"copyRaw": {
-			"suggested_key": {
-				"default": "Alt+Shift+C"
-			},
-			"description": "Copies the original link. Ex: Copies the original twitter link while in the nitter website"
-		},
-		"reverse": {
-			"suggested_key": {
-				"default": "Alt+Shift+O"
-			},
-			"description": "Redirect to the original link. Ex: Redirects to the original twitter link while in the nitter website"
-		},
-		"redirect": {
-			"suggested_key": {
-				"default": "Alt+Shift+R"
-			},
-			"description": "Redirect link. Ex: Redirects original twitter link to nitter"
-		}
-	},
-	"default_locale": "en",
-	"update_url": "https://raw.githubusercontent.com/libredirect/libredirect/master/src/updates/updates.xml",
-	"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAroWDSoSRZ1scj+eJRrvnhJbrqXTKnhQuxs6+AJg16sqr0bsMdFV+MSY4i4xnK+K5WOYkBliWXgUyk/wzicoAjOnSJddrL/Md4FuWHI2NVIkrlsLOrYkygi5OLqGPajRH/w8Cdmg7KzEpXe/OnYV0/qS8li8huEdTzdeLdhfbiVl1j3DOr4OJALQ7mPeeNFHFo/oVQ+OkSezWLezA5jUGfhtzPYV6u1TXzX7lCi8E/BbDbwkvvXOMcjXCv08kjdLOY2djCA2a6zr0xAb3q8DlexAMZ8vMof7AQRFtBKhLc9n9VFoipMMdBOVQQj/eIcRILBrmkcZNnJxFKiHNJ+NcZQIDAQAB"
-}
\ No newline at end of file
+  "name": "__MSG_extensionName__",
+  "description": "__MSG_extensionDescription__",
+  "version": "2.8.5",
+  "manifest_version": 2,
+  "browser_specific_settings": {
+    "gecko": {
+      "id": "7esoorv3@alefvanoon.anonaddy.me",
+      "strict_min_version": "89.0"
+    },
+    "gecko_android": {
+      "strict_min_version": "113.0"
+    }
+  },
+  "background": {
+    "page": "pages/background/background.html",
+    "persistent": true
+  },
+  "icons": {
+    "16": "assets/images/libredirect-16.png",
+    "32": "assets/images/libredirect-32.png",
+    "48": "assets/images/libredirect-48.png",
+    "128": "assets/images/libredirect-128.png"
+  },
+  "permissions": ["webRequest", "webRequestBlocking", "storage", "clipboardWrite", "contextMenus", "<all_urls>"],
+  "optional_permissions": ["bookmarks"],
+  "browser_action": {
+    "default_title": "__MSG_extensionName__",
+    "browser_style": false,
+    "default_popup": "pages/popup/popup.html",
+    "default_icon": {
+      "16": "assets/images/libredirect-16.png",
+      "32": "assets/images/libredirect-32.png",
+      "48": "assets/images/libredirect-48.png",
+      "128": "assets/images/libredirect-128.png"
+    }
+  },
+  "options_ui": {
+    "page": "pages/options/index.html",
+    "browser_style": false,
+    "open_in_tab": true
+  },
+  "chrome_settings_overrides": {
+    "search_provider": {
+      "name": "__MSG_extensionName__",
+      "keyword": "@libredirect",
+      "favicon_url": "https://raw.githubusercontent.com/libredirect/libredirect/master/src/assets/images/libredirect-16.png",
+      "search_url": "https://search.libredirect.invalid/?q={searchTerms}",
+      "encoding": "UTF-8",
+      "is_default": false
+    }
+  },
+  "commands": {
+    "switchInstance": {
+      "suggested_key": {
+        "default": "Alt+Shift+L"
+      },
+      "description": "__MSG_switchInstance__"
+    },
+    "copyRaw": {
+      "suggested_key": {
+        "default": "Alt+Shift+C"
+      },
+      "description": "Copies the original link. Ex: Copies the original twitter link while in the nitter website"
+    },
+    "reverse": {
+      "suggested_key": {
+        "default": "Alt+Shift+O"
+      },
+      "description": "Redirect to the original link. Ex: Redirects to the original twitter link while in the nitter website"
+    },
+    "redirect": {
+      "suggested_key": {
+        "default": "Alt+Shift+R"
+      },
+      "description": "Redirect link. Ex: Redirects original twitter link to nitter"
+    }
+  },
+  "default_locale": "en",
+  "update_url": "https://raw.githubusercontent.com/libredirect/libredirect/master/src/updates/updates.xml",
+  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAroWDSoSRZ1scj+eJRrvnhJbrqXTKnhQuxs6+AJg16sqr0bsMdFV+MSY4i4xnK+K5WOYkBliWXgUyk/wzicoAjOnSJddrL/Md4FuWHI2NVIkrlsLOrYkygi5OLqGPajRH/w8Cdmg7KzEpXe/OnYV0/qS8li8huEdTzdeLdhfbiVl1j3DOr4OJALQ7mPeeNFHFo/oVQ+OkSezWLezA5jUGfhtzPYV6u1TXzX7lCi8E/BbDbwkvvXOMcjXCv08kjdLOY2djCA2a6zr0xAb3q8DlexAMZ8vMof7AQRFtBKhLc9n9VFoipMMdBOVQQj/eIcRILBrmkcZNnJxFKiHNJ+NcZQIDAQAB"
+}
diff --git a/src/pages/background/background.html b/src/pages/background/background.html
index 542df18b..787d419e 100644
--- a/src/pages/background/background.html
+++ b/src/pages/background/background.html
@@ -1,7 +1,7 @@
-<!DOCTYPE html>
+<!doctype html>
 <html>
-	<head>
-		<meta charset="utf-8" />
-		<script type="module" src="background.js"></script>
-	</head>
+  <head>
+    <meta charset="utf-8" />
+    <script type="module" src="background.js"></script>
+  </head>
 </html>
diff --git a/src/pages/background/background.js b/src/pages/background/background.js
index 0c4d0923..e3d9fb31 100644
--- a/src/pages/background/background.js
+++ b/src/pages/background/background.js
@@ -7,310 +7,324 @@ const isChrome = browser.runtime.getBrowserInfo === undefined
 window.browser = window.browser || window.chrome
 
 browser.runtime.onInstalled.addListener(async details => {
-	if (details.previousVersion != browser.runtime.getManifest().version) {
-		// ^Used to prevent this running when debugging with auto-reload
-		if (details.reason == "install") {
-			if (!(await utils.getOptions())) {
-				await servicesHelper.initDefaults()
-			}
-			browser.runtime.openOptionsPage()
-		}
-		else if (details.reason == "update") {
-			if (details.previousVersion == '2.5.2') {
-				await servicesHelper.upgradeOptions()
-				await servicesHelper.processUpdate()
-			} else {
-				await servicesHelper.processUpdate()
-			}
-		}
-	}
+  if (details.previousVersion != browser.runtime.getManifest().version) {
+    // ^Used to prevent this running when debugging with auto-reload
+    if (details.reason == "install") {
+      if (!(await utils.getOptions())) {
+        await servicesHelper.initDefaults()
+      }
+      browser.runtime.openOptionsPage()
+    } else if (details.reason == "update") {
+      if (details.previousVersion == "2.5.2") {
+        await servicesHelper.upgradeOptions()
+        await servicesHelper.processUpdate()
+      } else {
+        await servicesHelper.processUpdate()
+      }
+    }
+  }
 })
 
 let tabIdRedirects = {}
 
 // true == Always redirect, false == Never redirect, null/undefined == follow options for services
 browser.webRequest.onBeforeRequest.addListener(
-	details => {
-		const url = new URL(details.url)
-		const old_href = url.href
-		if (new RegExp(/^chrome-extension:\/{2}.*\/instances\/.*.json$/).test(url.href) && details.type == "xmlhttprequest") return
-		let initiator
-		try {
-			if (details.originUrl) initiator = new URL(details.originUrl)
-			else if (details.initiator && details.initiator !== "null") initiator = new URL(details.initiator)
-		} catch {
-			return null
-		}
-		if (tabIdRedirects[details.tabId] == false) return null
-		let newUrl = servicesHelper.redirect(url, details.type, initiator, tabIdRedirects[details.tabId], details.incognito)
+  details => {
+    const url = new URL(details.url)
+    const old_href = url.href
+    if (new RegExp(/^chrome-extension:\/{2}.*\/instances\/.*.json$/).test(url.href) && details.type == "xmlhttprequest")
+      return
+    let initiator
+    try {
+      if (details.originUrl) initiator = new URL(details.originUrl)
+      else if (details.initiator && details.initiator !== "null") initiator = new URL(details.initiator)
+    } catch {
+      return null
+    }
+    if (tabIdRedirects[details.tabId] == false) return null
+    let newUrl = servicesHelper.redirect(url, details.type, initiator, tabIdRedirects[details.tabId], details.incognito)
 
-		if (details.frameAncestors && details.frameAncestors.length > 0 && servicesHelper.isException(new URL(details.frameAncestors[0].url))) newUrl = null
+    if (
+      details.frameAncestors &&
+      details.frameAncestors.length > 0 &&
+      servicesHelper.isException(new URL(details.frameAncestors[0].url))
+    )
+      newUrl = null
 
-		if (servicesHelper.isException(url)) {
-			if (details.type == "main_frame")
-				newUrl = "BYPASSTAB"
-			else
-				return null
-		}
+    if (servicesHelper.isException(url)) {
+      if (details.type == "main_frame") newUrl = "BYPASSTAB"
+      else return null
+    }
 
-		if (!newUrl) {
-			const match = url.href.match(/^https?:\/{2}.*\.libredirect\.invalid.*/)
-			if (match) {
-				browser.tabs.update({
-					url: browser.runtime.getURL(`/pages/messages/no_instance.html`)
-				});
-			}
-		}
+    if (!newUrl) {
+      const match = url.href.match(/^https?:\/{2}.*\.libredirect\.invalid.*/)
+      if (match) {
+        browser.tabs.update({
+          url: browser.runtime.getURL(`/pages/messages/no_instance.html`),
+        })
+      }
+    }
 
-		if (newUrl) {
-			if (newUrl === "CANCEL") {
-				console.log(`Canceled ${url}`)
-				return { cancel: true }
-			}
-			if (newUrl === "BYPASSTAB") {
-				console.log(`Bypassed ${details.tabId} ${url}`)
-				if (tabIdRedirects[details.tabId] != false) tabIdRedirects[details.tabId] = false
-				return null
-			}
-			console.log("Redirecting", old_href, "=>", newUrl)
-			return { redirectUrl: newUrl }
-		}
-		return null
-	},
-	{ urls: ["<all_urls>"] },
-	["blocking"]
+    if (newUrl) {
+      if (newUrl === "CANCEL") {
+        console.log(`Canceled ${url}`)
+        return { cancel: true }
+      }
+      if (newUrl === "BYPASSTAB") {
+        console.log(`Bypassed ${details.tabId} ${url}`)
+        if (tabIdRedirects[details.tabId] != false) tabIdRedirects[details.tabId] = false
+        return null
+      }
+      console.log("Redirecting", old_href, "=>", newUrl)
+      return { redirectUrl: newUrl }
+    }
+    return null
+  },
+  { urls: ["<all_urls>"] },
+  ["blocking"]
 )
 
 browser.tabs.onRemoved.addListener(tabId => {
-	if (tabIdRedirects[tabId] != undefined) {
-		delete tabIdRedirects[tabId]
-		console.log(`Removed tab ${tabId} from tabIdRedirects`)
-	}
+  if (tabIdRedirects[tabId] != undefined) {
+    delete tabIdRedirects[tabId]
+    console.log(`Removed tab ${tabId} from tabIdRedirects`)
+  }
 })
 
 browser.commands.onCommand.addListener(async command => {
-	browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-		const url = new URL(tabs[0].url)
-		switch (command) {
-			case "switchInstance": {
-				const newUrl = await servicesHelper.switchInstance(url)
-				if (newUrl) browser.tabs.update({ url: newUrl })
-				break
-			}
-			case "copyRaw":
-				servicesHelper.copyRaw(url)
-				break
-			case "redirect":
-				browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-					if (tabs[0].url) {
-						const url = new URL(tabs[0].url)
-						const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
-						if (newUrl) {
-							browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
-								tabIdRedirects[tabs[0].id] = true
-							})
-						}
-					}
-				})
-				break
-			case "reverse":
-				browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-					if (tabs[0].url) {
-						const url = new URL(tabs[0].url)
-						const newUrl = await servicesHelper.reverse(url)
-						if (newUrl) {
-							browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
-								tabIdRedirects[tabs[0].id] = false
-							})
-						}
-					}
-				})
-				break
-		}
-	})
+  browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
+    const url = new URL(tabs[0].url)
+    switch (command) {
+      case "switchInstance": {
+        const newUrl = await servicesHelper.switchInstance(url)
+        if (newUrl) browser.tabs.update({ url: newUrl })
+        break
+      }
+      case "copyRaw":
+        servicesHelper.copyRaw(url)
+        break
+      case "redirect":
+        browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
+          if (tabs[0].url) {
+            const url = new URL(tabs[0].url)
+            const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
+            if (newUrl) {
+              browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
+                tabIdRedirects[tabs[0].id] = true
+              })
+            }
+          }
+        })
+        break
+      case "reverse":
+        browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
+          if (tabs[0].url) {
+            const url = new URL(tabs[0].url)
+            const newUrl = await servicesHelper.reverse(url)
+            if (newUrl) {
+              browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
+                tabIdRedirects[tabs[0].id] = false
+              })
+            }
+          }
+        })
+        break
+    }
+  })
 })
 
-browser.contextMenus.create({ id: "settingsTab", title: browser.i18n.getMessage("settings"), contexts: ["browser_action"] })
-browser.contextMenus.create({ id: "switchInstanceTab", title: browser.i18n.getMessage("switchInstance"), contexts: ["browser_action"] })
-browser.contextMenus.create({ id: "copyReverseTab", title: 'Copy Original', contexts: ["browser_action"] })
-browser.contextMenus.create({ id: "redirectTab", title: 'Redirect', contexts: ["browser_action"] })
-browser.contextMenus.create({ id: "reverseTab", title: 'Redirect To Original', contexts: ["browser_action"] })
+browser.contextMenus.create({
+  id: "settingsTab",
+  title: browser.i18n.getMessage("settings"),
+  contexts: ["browser_action"],
+})
+browser.contextMenus.create({
+  id: "switchInstanceTab",
+  title: browser.i18n.getMessage("switchInstance"),
+  contexts: ["browser_action"],
+})
+browser.contextMenus.create({ id: "copyReverseTab", title: "Copy Original", contexts: ["browser_action"] })
+browser.contextMenus.create({ id: "redirectTab", title: "Redirect", contexts: ["browser_action"] })
+browser.contextMenus.create({ id: "reverseTab", title: "Redirect To Original", contexts: ["browser_action"] })
 
-browser.contextMenus.create({ id: "redirectLink", title: 'Redirect', contexts: ["link"] })
-browser.contextMenus.create({ id: "redirectLinkInNewTab", title: 'Redirect In New Tab', contexts: ["link"] })
-browser.contextMenus.create({ id: "reverseLink", title: 'Redirect To Original', contexts: ["link"] })
-browser.contextMenus.create({ id: "reverseLinkInNewTab", title: 'Redirect To Original In New Tab', contexts: ["link"] })
-browser.contextMenus.create({ id: "copyReverseLink", title: 'Copy Original', contexts: ["link"] })
-browser.contextMenus.create({ id: "bypassLink", title: 'Bypass', contexts: ["link"] })
-browser.contextMenus.create({ id: "bypassLinkInNewTab", title: 'Bypass In New Tab', contexts: ["link"] })
+browser.contextMenus.create({ id: "redirectLink", title: "Redirect", contexts: ["link"] })
+browser.contextMenus.create({ id: "redirectLinkInNewTab", title: "Redirect In New Tab", contexts: ["link"] })
+browser.contextMenus.create({ id: "reverseLink", title: "Redirect To Original", contexts: ["link"] })
+browser.contextMenus.create({ id: "reverseLinkInNewTab", title: "Redirect To Original In New Tab", contexts: ["link"] })
+browser.contextMenus.create({ id: "copyReverseLink", title: "Copy Original", contexts: ["link"] })
+browser.contextMenus.create({ id: "bypassLink", title: "Bypass", contexts: ["link"] })
+browser.contextMenus.create({ id: "bypassLinkInNewTab", title: "Bypass In New Tab", contexts: ["link"] })
 
 if (!isChrome) {
-	browser.contextMenus.create({ id: "redirectBookmark", title: 'Redirect', contexts: ["bookmark"] })
-	browser.contextMenus.create({ id: "redirectBookmarkInNewTab", title: 'Redirect In New Tab', contexts: ["bookmark"] })
-	browser.contextMenus.create({ id: "reverseBookmark", title: 'Redirect To Original', contexts: ["bookmark"] })
-	browser.contextMenus.create({ id: "reverseBookmarkInNewTab", title: 'Redirect To Original In New Tab', contexts: ["bookmark"] })
-	browser.contextMenus.create({ id: "copyReverseBookmark", title: 'Copy Original', contexts: ["bookmark"] })
-	browser.contextMenus.create({ id: "bypassBookmark", title: 'Bypass', contexts: ["bookmark"] })
-	browser.contextMenus.create({ id: "bypassBookmarkInNewTab", title: 'Bypass In New Tab', contexts: ["bookmark"] })
+  browser.contextMenus.create({ id: "redirectBookmark", title: "Redirect", contexts: ["bookmark"] })
+  browser.contextMenus.create({ id: "redirectBookmarkInNewTab", title: "Redirect In New Tab", contexts: ["bookmark"] })
+  browser.contextMenus.create({ id: "reverseBookmark", title: "Redirect To Original", contexts: ["bookmark"] })
+  browser.contextMenus.create({
+    id: "reverseBookmarkInNewTab",
+    title: "Redirect To Original In New Tab",
+    contexts: ["bookmark"],
+  })
+  browser.contextMenus.create({ id: "copyReverseBookmark", title: "Copy Original", contexts: ["bookmark"] })
+  browser.contextMenus.create({ id: "bypassBookmark", title: "Bypass", contexts: ["bookmark"] })
+  browser.contextMenus.create({ id: "bypassBookmarkInNewTab", title: "Bypass In New Tab", contexts: ["bookmark"] })
 }
 
-browser.contextMenus.onClicked.addListener(async (info) => {
-	switch (info.menuItemId) {
-		case 'switchInstanceTab': {
-			const url = new URL(info.pageUrl)
-			const newUrl = await servicesHelper.switchInstance(url)
-			if (newUrl) browser.tabs.update({ url: newUrl })
-			return
-		}
-		case 'settingsTab':
-			browser.runtime.openOptionsPage()
-			return
-		case 'copyReverseTab':
-			browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-				if (tabs[0].url) {
-					const url = new URL(tabs[0].url)
-					servicesHelper.copyRaw(url)
-				}
-			})
-			return
-		case 'reverseTab':
-			browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-				if (tabs[0].url) {
-					const url = new URL(tabs[0].url)
-					const newUrl = await servicesHelper.reverse(url)
-					if (newUrl) {
-						browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
-							tabIdRedirects[tabs[0].id] = false
-						})
-					}
-				}
-			})
-			return
-		case 'redirectTab':
-			browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-				if (tabs[0].url) {
-					const url = new URL(tabs[0].url)
-					const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
-					if (newUrl) {
-						browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
-							tabIdRedirects[tabs[0].id] = true
-						})
-					}
-				}
-			})
-			return
-		case 'copyReverseLink': {
-			const url = new URL(info.linkUrl)
-			await servicesHelper.copyRaw(url)
-			return
-		}
-		case 'redirectLink':
-		case 'redirectLinkInNewTab': {
-			const url = new URL(info.linkUrl)
-			const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
-			if (newUrl) {
-				if (info.menuItemId == "redirectLink") browser.tabs.update({ url: newUrl })
-				else browser.tabs.create({ url: newUrl })
-			}
-			return
-		}
-		case 'reverseLink':
-		case 'reverseLinkInNewTab': {
-			const url = new URL(info.linkUrl)
-			const newUrl = await servicesHelper.reverse(url)
-			if (newUrl) {
-				if (info.menuItemId == "reverseLink") {
-					browser.tabs.update({ url: newUrl }, tab => {
-						tabIdRedirects[tab.id] = false
-					})
-				} else {
-					browser.tabs.create({ url: newUrl }, tab => {
-						tabIdRedirects[tab.id] = false
-					})
-				}
-			}
-			return
-		}
-		case 'bypassLink':
-		case 'bypassLinkInNewTab': {
-			const url = new URL(info.linkUrl)
-			if (info.menuItemId == "bypassLink") {
-				browser.tabs.update({ url: url.href }, tab => {
-					tabIdRedirects[tab.id] = false
-				})
-			} else {
-				browser.tabs.create({ url: url.href }, tab => {
-					tabIdRedirects[tab.id] = false
-				})
-			}
-			return
-		}
-		case 'copyReverseBookmark':
-			browser.bookmarks.get(info.bookmarkId, bookmarks => {
-				const url = new URL(bookmarks[0].url)
-				servicesHelper.copyRaw(url)
-			});
-			return
-		case 'redirectBookmark':
-		case 'redirectBookmarkInNewTab':
-			browser.bookmarks.get(info.bookmarkId, bookmarks => {
-				const url = new URL(bookmarks[0].url)
-				const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
-				if (newUrl) {
-					if (info.menuItemId == 'redirectBookmark') browser.tabs.update({ url: newUrl })
-					else browser.tabs.create({ url: newUrl })
-				}
-			})
-			return
-		case 'reverseBookmark':
-		case 'reverseBookmarkInNewTab':
-			browser.bookmarks.get(info.bookmarkId, async bookmarks => {
-				const url = new URL(bookmarks[0].url)
-				const newUrl = await servicesHelper.reverse(url)
-				if (newUrl) {
-					if (info.menuItemId == "reverseBookmark") {
-						browser.tabs.update({ url: newUrl }, tab => {
-							tabIdRedirects[tab.id] = false
-						})
-					} else {
-						browser.tabs.create({ url: newUrl }, tab => {
-							tabIdRedirects[tab.id] = false
-						})
-					}
-				}
-			})
-			return
-		case 'bypassBookmark':
-		case 'bypassBookmarkInNewTab':
-			browser.bookmarks.get(info.bookmarkId, async bookmarks => {
-				const url = new URL(bookmarks[0].url)
-				if (info.menuItemId == "bypassBookmark") {
-					browser.tabs.update({ url: url.href }, tab => tabIdRedirects[tab.id] = false)
-				} else {
-					browser.tabs.create({ url: url.href }, tab => tabIdRedirects[tab.id] = false)
-				}
-				return
-			})
-	}
+browser.contextMenus.onClicked.addListener(async info => {
+  switch (info.menuItemId) {
+    case "switchInstanceTab": {
+      const url = new URL(info.pageUrl)
+      const newUrl = await servicesHelper.switchInstance(url)
+      if (newUrl) browser.tabs.update({ url: newUrl })
+      return
+    }
+    case "settingsTab":
+      browser.runtime.openOptionsPage()
+      return
+    case "copyReverseTab":
+      browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
+        if (tabs[0].url) {
+          const url = new URL(tabs[0].url)
+          servicesHelper.copyRaw(url)
+        }
+      })
+      return
+    case "reverseTab":
+      browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
+        if (tabs[0].url) {
+          const url = new URL(tabs[0].url)
+          const newUrl = await servicesHelper.reverse(url)
+          if (newUrl) {
+            browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
+              tabIdRedirects[tabs[0].id] = false
+            })
+          }
+        }
+      })
+      return
+    case "redirectTab":
+      browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
+        if (tabs[0].url) {
+          const url = new URL(tabs[0].url)
+          const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
+          if (newUrl) {
+            browser.tabs.update(tabs[0].id, { url: newUrl }, () => {
+              tabIdRedirects[tabs[0].id] = true
+            })
+          }
+        }
+      })
+      return
+    case "copyReverseLink": {
+      const url = new URL(info.linkUrl)
+      await servicesHelper.copyRaw(url)
+      return
+    }
+    case "redirectLink":
+    case "redirectLinkInNewTab": {
+      const url = new URL(info.linkUrl)
+      const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
+      if (newUrl) {
+        if (info.menuItemId == "redirectLink") browser.tabs.update({ url: newUrl })
+        else browser.tabs.create({ url: newUrl })
+      }
+      return
+    }
+    case "reverseLink":
+    case "reverseLinkInNewTab": {
+      const url = new URL(info.linkUrl)
+      const newUrl = await servicesHelper.reverse(url)
+      if (newUrl) {
+        if (info.menuItemId == "reverseLink") {
+          browser.tabs.update({ url: newUrl }, tab => {
+            tabIdRedirects[tab.id] = false
+          })
+        } else {
+          browser.tabs.create({ url: newUrl }, tab => {
+            tabIdRedirects[tab.id] = false
+          })
+        }
+      }
+      return
+    }
+    case "bypassLink":
+    case "bypassLinkInNewTab": {
+      const url = new URL(info.linkUrl)
+      if (info.menuItemId == "bypassLink") {
+        browser.tabs.update({ url: url.href }, tab => {
+          tabIdRedirects[tab.id] = false
+        })
+      } else {
+        browser.tabs.create({ url: url.href }, tab => {
+          tabIdRedirects[tab.id] = false
+        })
+      }
+      return
+    }
+    case "copyReverseBookmark":
+      browser.bookmarks.get(info.bookmarkId, bookmarks => {
+        const url = new URL(bookmarks[0].url)
+        servicesHelper.copyRaw(url)
+      })
+      return
+    case "redirectBookmark":
+    case "redirectBookmarkInNewTab":
+      browser.bookmarks.get(info.bookmarkId, bookmarks => {
+        const url = new URL(bookmarks[0].url)
+        const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
+        if (newUrl) {
+          if (info.menuItemId == "redirectBookmark") browser.tabs.update({ url: newUrl })
+          else browser.tabs.create({ url: newUrl })
+        }
+      })
+      return
+    case "reverseBookmark":
+    case "reverseBookmarkInNewTab":
+      browser.bookmarks.get(info.bookmarkId, async bookmarks => {
+        const url = new URL(bookmarks[0].url)
+        const newUrl = await servicesHelper.reverse(url)
+        if (newUrl) {
+          if (info.menuItemId == "reverseBookmark") {
+            browser.tabs.update({ url: newUrl }, tab => {
+              tabIdRedirects[tab.id] = false
+            })
+          } else {
+            browser.tabs.create({ url: newUrl }, tab => {
+              tabIdRedirects[tab.id] = false
+            })
+          }
+        }
+      })
+      return
+    case "bypassBookmark":
+    case "bypassBookmarkInNewTab":
+      browser.bookmarks.get(info.bookmarkId, async bookmarks => {
+        const url = new URL(bookmarks[0].url)
+        if (info.menuItemId == "bypassBookmark") {
+          browser.tabs.update({ url: url.href }, tab => (tabIdRedirects[tab.id] = false))
+        } else {
+          browser.tabs.create({ url: url.href }, tab => (tabIdRedirects[tab.id] = false))
+        }
+        return
+      })
+  }
 })
 
 browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
-	if (request == "reverseTab") {
-		browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-			if (tabs[0].url) {
-				const url = new URL(tabs[0].url)
-				const newUrl = await servicesHelper.reverse(url)
-				if (newUrl) browser.tabs.update(tabs[0].id, { url: newUrl }, () => tabIdRedirects[tabs[0].id] = false)
-			}
-		})
-	}
-	else if (request == "redirectTab") {
-		browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-			if (tabs[0].url) {
-				const url = new URL(tabs[0].url)
-				const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
-				if (newUrl) browser.tabs.update(tabs[0].id, { url: newUrl }, () => tabIdRedirects[tabs[0].id] = true)
-			}
-		})
-	}
-})
\ No newline at end of file
+  if (request == "reverseTab") {
+    browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
+      if (tabs[0].url) {
+        const url = new URL(tabs[0].url)
+        const newUrl = await servicesHelper.reverse(url)
+        if (newUrl) browser.tabs.update(tabs[0].id, { url: newUrl }, () => (tabIdRedirects[tabs[0].id] = false))
+      }
+    })
+  } else if (request == "redirectTab") {
+    browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
+      if (tabs[0].url) {
+        const url = new URL(tabs[0].url)
+        const newUrl = servicesHelper.redirect(url, "main_frame", null, true)
+        if (newUrl) browser.tabs.update(tabs[0].id, { url: newUrl }, () => (tabIdRedirects[tabs[0].id] = true))
+      }
+    })
+  }
+})
diff --git a/src/pages/messages/no_instance.html b/src/pages/messages/no_instance.html
index 76ec19cf..358ff506 100644
--- a/src/pages/messages/no_instance.html
+++ b/src/pages/messages/no_instance.html
@@ -1,26 +1,24 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en">
-
-<head>
-    <meta charset="UTF-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <link href="../stylesheets/styles.css" rel="stylesheet">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <link href="../stylesheets/styles.css" rel="stylesheet" />
     <title>No instances found</title>
     <style>
-        #body {
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            height: 100vh;
-        }
+      #body {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        height: 100vh;
+      }
     </style>
-</head>
+  </head>
 
-<body>
+  <body>
     <div id="body">
-        <h1>You have no instance selected for this frontend</h1>
+      <h1>You have no instance selected for this frontend</h1>
     </div>
-</body>
-
-</html>
\ No newline at end of file
+  </body>
+</html>
diff --git a/src/pages/options/index.js b/src/pages/options/index.js
deleted file mode 100644
index 0066df0d..00000000
--- a/src/pages/options/index.js
+++ /dev/null
@@ -1,390 +0,0 @@
-import utils from "../../assets/javascripts/utils.js"
-
-let config,
-	options,
-	blacklist,
-	redirects,
-	divs = {}
-
-for (const a of document.getElementById("links").getElementsByTagName("a")) {
-	if (!a.href.includes("https://")) {
-		a.addEventListener("click", e => {
-			const path = a.getAttribute("href").replace("#", "")
-			loadPage(path)
-			e.preventDefault()
-		})
-	}
-}
-
-config = await utils.getConfig()
-options = await utils.getOptions()
-
-/**
- * @param {string} service
- */
-async function changeFrontendsSettings(service) {
-	options = await utils.getOptions()
-	const opacityDiv = document.getElementById(`${service}-opacity`)
-	if (document.getElementById(`${service}-enabled`).checked) {
-		opacityDiv.style.pointerEvents = 'auto'
-		opacityDiv.style.opacity = 1
-		opacityDiv.style.userSelect = 'auto'
-	} else {
-		opacityDiv.style.pointerEvents = 'none'
-		opacityDiv.style.opacity = 0.4
-		opacityDiv.style.userSelect = 'none'
-	}
-	for (const frontend in config.services[service].frontends) {
-		if (config.services[service].frontends[frontend].instanceList) {
-			const frontendDiv = document.getElementById(frontend)
-			if (typeof divs[service].frontend !== "undefined") {
-				if (
-					frontend == divs[service].frontend.value
-					||
-					(config.services[service].frontends[divs[service].frontend.value].desktopApp && divs[service].embedFrontend && frontend == divs[service].embedFrontend.value)
-				) {
-					frontendDiv.style.display = ""
-					if (config.services[service].frontends[frontend].localhost === true) {
-						document.getElementById(`${service}-instance-div`).style.display = ""
-
-						if (options[service].instance == "localhost") {
-							frontendDiv.style.display = "none"
-						}
-					} else {
-						document.getElementById(`${service}-instance-div`).style.display = "none"
-					}
-				} else {
-					frontendDiv.style.display = "none"
-				}
-			}
-		}
-	}
-	if (document.getElementById(`${service}-redirectType`)) {
-		const frontend = options[service].frontend
-		if (config.services[service].frontends[frontend].embeddable) {
-			document.getElementById(`${service}-redirectType`).innerHTML = `
-			<option value="both" data-localise="__MSG_both__">both</options>
-			<option value="sub_frame" data-localise="__MSG_onlyEmbedded__">Only Embedded</option>
-			<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>
-			`
-		}
-		else if (config.services[service].frontends[frontend].desktopApp && Object.values(config.services[service].frontends).some(frontend => frontend.embeddable)) {
-			document.getElementById(`${service}-redirectType`).innerHTML = `
-			<option value="both" data-localise="__MSG_both__">both</options>
-			<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>
-			`
-			if (options[service].redirectType == "sub_frame") {
-				options[service].redirectType = "main_frame"
-				browser.storage.local.set({ options })
-			}
-		} else {
-			document.getElementById(`${service}-redirectType`).innerHTML =
-				'<option value="main_frame" data-localise="__MSG_onlyNotEmbedded__">Only Not Embedded</option>'
-			options[service].redirectType = "main_frame"
-
-			browser.storage.local.set({ options })
-		}
-		document.getElementById(`${service}-redirectType`).value = options[service].redirectType
-		if (config.services[service].frontends[frontend].desktopApp && options[service].redirectType != "main_frame") {
-			document.getElementById(`${service}-embedFrontend-div`).style.display = ''
-			document.getElementById(divs[service].embedFrontend.value).style.display = ''
-		}
-		else if (config.services[service].frontends[frontend].desktopApp && options[service].redirectType == "main_frame") {
-			document.getElementById(`${service}-embedFrontend-div`).style.display = 'none'
-			document.getElementById(divs[service].embedFrontend.value).style.display = 'none'
-		} else {
-			document.getElementById(`${service}-embedFrontend-div`).style.display = 'none'
-		}
-	}
-	const frontend_name_element = document.getElementById(`${service}_page`).getElementsByClassName("frontend_name")[0]
-	frontend_name_element.href = config.services[service].frontends[divs[service].frontend.value].url
-}
-
-/**
- * @param {string} path
- */
-async function loadPage(path) {
-	options = await utils.getOptions()
-	for (const section of document.getElementById("pages").getElementsByTagName("section")) section.style.display = "none"
-	document.getElementById(`${path}_page`).style.display = "block"
-
-	for (const element of document.getElementsByClassName("title")) {
-		const a = element.getElementsByTagName('a')[0]
-		if (a.getAttribute("href") == `#${path}`) {
-			element.classList.add("selected")
-		} else {
-			element.classList.remove("selected")
-		}
-	}
-
-	for (const service in config.services) {
-		if (options[service].enabled) {
-			document.getElementById(`${service}-link`).style.opacity = 1
-		} else {
-			document.getElementById(`${service}-link`).style.opacity = 0.4
-		}
-	}
-
-	window.history.pushState({ id: "100" }, "Page 2", `/pages/options/index.html#${path}`)
-
-	if (path != 'general') {
-		const service = path;
-		divs[service] = {}
-		for (const option in config.services[service].options) {
-			divs[service][option] = document.getElementById(`${service}-${option}`)
-			if (typeof config.services[service].options[option] == "boolean") divs[service][option].checked = options[service][option]
-			else divs[service][option].value = options[service][option]
-			divs[service][option].addEventListener("change", async () => {
-				let options = await utils.getOptions()
-				if (typeof config.services[service].options[option] == "boolean")
-					options[service][option] = divs[service][option].checked
-				else
-					options[service][option] = divs[service][option].value
-				browser.storage.local.set({ options })
-				changeFrontendsSettings(service)
-			})
-		}
-		changeFrontendsSettings(service)
-
-		blacklist = await utils.getBlacklist(options)
-		redirects = await utils.getList(options)
-		for (const frontend in config.services[service].frontends) {
-			if (config.services[service].frontends[frontend].instanceList) {
-				if (redirects == 'disabled' || blacklist == 'disabled') {
-					document.getElementById(frontend).getElementsByClassName('clearnet')[0].style.display = 'none'
-					document.getElementById(frontend).getElementsByClassName('ping')[0].style.display = 'none'
-				}
-				else if (!redirects || !blacklist) {
-					document.getElementById(frontend)
-						.getElementsByClassName('clearnet')[0]
-						.getElementsByClassName("checklist")[0]
-						.getElementsByClassName('loading')[0]
-						.innerHTML = 'Could not fetch instances.'
-				}
-				else {
-					createList(frontend)
-				}
-			}
-		}
-
-		for (const frontend in config.services[service].frontends) {
-			if (config.services[service].frontends[frontend].instanceList) {
-				processCustomInstances(frontend)
-				document.getElementById(`ping-${frontend}`).addEventListener("click", async () => {
-					document.getElementById(`ping-${frontend}`).getElementsByTagName('x')[0].innerHTML = "Pinging..."
-					await ping(frontend)
-					document.getElementById(`ping-${frontend}`).getElementsByTagName('x')[0].innerHTML = "Ping instances"
-				})
-			}
-		}
-	}
-}
-
-async function calcCustomInstances(frontend) {
-	let options = await utils.getOptions()
-	let customInstances = options[frontend]
-	const pingCache = await utils.getPingCache()
-
-	document.getElementById(frontend).getElementsByClassName("custom-checklist")[0].innerHTML = customInstances
-		.map(
-			x => {
-				const time = pingCache[x]
-				if (time) {
-					const { color, text } = processTime(time)
-					var timeText = `<span class="ping" style="color:${color};">${text}</span>`
-				}
-				const custom = isCustomInstance(frontend, x) ? "" : `<span>custom</span>`
-				return `<div>
-							<x>
-								<a href="${x}" target="_blank">${x}</a>
-								${timeText}
-								${custom}
-							</x>
-							<button class="add clear-${x}">
-								<svg xmlns="https://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor">
-									<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
-								</svg>
-							</button>
-						</div>
-						<hr>`
-			})
-		.join("\n")
-	for (const item of customInstances) {
-		document.getElementById(frontend).getElementsByClassName(`clear-${item}`)[0].addEventListener("click", async () => {
-			const index = customInstances.indexOf(item)
-			if (index > -1) customInstances.splice(index, 1)
-			options = await utils.getOptions()
-			options[frontend] = customInstances
-			browser.storage.local.set({ options }, async () => {
-				calcCustomInstances(frontend)
-				createList(frontend)
-			})
-		})
-	}
-}
-
-async function processCustomInstances(frontend) {
-	calcCustomInstances(frontend)
-	document.getElementById(frontend).getElementsByClassName("custom-instance-form")[0].addEventListener("submit", async event => {
-		event.preventDefault()
-		let options = await utils.getOptions()
-		let customInstances = options[frontend]
-		let frontendCustomInstanceInput = document.getElementById(frontend).getElementsByClassName("custom-instance")[0]
-		try {
-			var url = new URL(frontendCustomInstanceInput.value)
-		} catch (error) {
-			return
-		}
-		let protocolHostVar = utils.protocolHost(url)
-		if (frontendCustomInstanceInput.validity.valid) {
-			if (!customInstances.includes(protocolHostVar)) {
-				customInstances.push(protocolHostVar)
-				options = await utils.getOptions()
-				options[frontend] = customInstances
-				browser.storage.local.set({ options }, () => {
-					frontendCustomInstanceInput.value = ""
-					calcCustomInstances(frontend)
-				})
-			}
-		}
-	})
-}
-
-/**
- * @param {string} frontend
- */
-async function createList(frontend) {
-	const pingCache = await utils.getPingCache()
-	const options = await utils.getOptions()
-	for (const network in config.networks) {
-		const checklist = document.getElementById(frontend).getElementsByClassName(network)[0].getElementsByClassName("checklist")[0]
-
-		if (!redirects[frontend]) {
-			checklist.innerHTML = '<div class="block block-option">No instances found.</div>'
-			break
-		}
-
-		const instances = redirects[frontend][network]
-		if (!instances || instances.length === 0) continue
-
-		document.getElementById(frontend).getElementsByClassName("custom-instance")[0].placeholder = redirects[frontend].clearnet[0]
-
-		instances.sort((a, b) => blacklist.cloudflare.includes(a) && !blacklist.cloudflare.includes(b))
-		const content = instances
-			.map(x => {
-				const cloudflare = blacklist.cloudflare.includes(x) ?
-					`<a target="_blank" href="https://libredirect.github.io/docs.html#instances">
-                        <span style="color:red;">cloudflare</span>
-                    </a>` : ""
-
-				let time = pingCache[x]
-				let timeText = ""
-				if (time) {
-					const { color, text } = processTime(time)
-					timeText = `<span class="ping" style="color:${color};">${text}</span>`
-				}
-
-				const chosen = options[frontend].includes(x) ? `<span style="color:grey;">chosen</span>` : ""
-
-				const warnings = [cloudflare, timeText, chosen].join(" ")
-				return `<div class="frontend">
-                            <x>
-                                <a href="${x}" target="_blank">${x}</a>
-								${warnings}
-                            </x>
-                            <button class="add add-${x}">
-                                <svg xmlns="https://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor">
-                                    <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
-                                </svg>
-                            </button>
-                        </div>`
-			})
-
-		checklist.innerHTML = [
-			`<div class="block block-option">
-                <label>${utils.camelCase(network)}</label>
-            </div>`,
-			...content,
-			"<br>"
-		].join("\n<hr>\n")
-
-		for (const instance of instances) {
-			checklist.getElementsByClassName(`add-${instance}`)[0]
-				.addEventListener("click", async () => {
-					let options = await utils.getOptions()
-					if (!options[frontend].includes(instance)) {
-						options[frontend].push(instance)
-						browser.storage.local.set({ options }, () => {
-							calcCustomInstances(frontend)
-							createList(frontend)
-						})
-					}
-				})
-		}
-	}
-}
-
-const r = window.location.href.match(/#(.*)/)
-if (r) loadPage(r[1])
-else loadPage("general")
-
-/**
- * @param {string} frontend
- */
-async function ping(frontend) {
-	const instanceElements = [
-		...document.getElementById(frontend).getElementsByClassName("custom-checklist")[0].getElementsByTagName('x'),
-		...document.getElementById(frontend).getElementsByClassName('clearnet')[0].getElementsByTagName('x')
-	]
-
-	let pingCache = await utils.getPingCache()
-	let redundancyList = {}
-	for (const element of instanceElements) {
-		let span = element.getElementsByClassName('ping')[0]
-		if (!span) span = document.createElement('span')
-		span.classList = ['ping']
-		span.innerHTML = '<span style="color:lightblue">pinging...</span>'
-		element.appendChild(span)
-		const href = element.getElementsByTagName('a')[0].href
-		const innerHTML = element.getElementsByTagName('a')[0].innerHTML
-		const time = redundancyList[innerHTML] ?? await utils.ping(href)
-		const { color, text } = processTime(time)
-		span.innerHTML = `<span style="color:${color};">${text}</span>`
-		pingCache[innerHTML] = time
-		redundancyList[innerHTML] = time
-
-		browser.storage.local.set({ pingCache })
-	}
-}
-
-/**
- * @param {number} time
- */
-function processTime(time) {
-	let text
-	let color
-	if (time < 5000) {
-		text = `${time}ms`
-		if (time <= 1000) color = "green"
-		else if (time <= 2000) color = "orange"
-	}
-	else if (time >= 5000) {
-		color = "red"
-		if (time == 5000) text = "5000ms+"
-		if (time > 5000) text = `Error: ${time - 5000}`
-	}
-	else {
-		color = "red"
-		text = 'Server not found'
-	}
-	return { color, text }
-}
-
-function isCustomInstance(frontend, instance) {
-	for (const network in config.networks) {
-		if (!redirects[frontend]) return false;
-		const instances = redirects[frontend][network]
-		if (instances.includes(instance)) return true
-	}
-	return false
-}
\ No newline at end of file
diff --git a/src/pages/options/index.pug b/src/pages/options/index.pug
deleted file mode 100644
index 4e19b087..00000000
--- a/src/pages/options/index.pug
+++ /dev/null
@@ -1,10 +0,0 @@
-doctype html
-html(id="elementToShowWithJavaScript" lang="en")
-    include /src/pages/widgets/head
-    body(class="option" dir="auto")
-        include /src/pages/widgets/links
-        div#pages
-            include /src/pages/options/widgets/general
-            include /src/pages/options/widgets/services
-    script(type="module" src="./index.js")
-    
\ No newline at end of file
diff --git a/src/pages/options/init.js b/src/pages/options/init.js
index baf3eebe..07da1859 100644
--- a/src/pages/options/init.js
+++ b/src/pages/options/init.js
@@ -5,46 +5,46 @@ import utils from "../../assets/javascripts/utils.js"
 import servicesHelper from "../../assets/javascripts/services.js"
 
 if (!(await utils.getOptions())) {
-	await servicesHelper.initDefaults()
+  await servicesHelper.initDefaults()
 }
 
 async function changeTheme() {
-	switch ((await utils.getOptions()).theme) {
-		case "dark":
-			document.body.classList.add("dark-theme")
-			document.body.classList.remove("light-theme")
-			for (const element of document.body.getElementsByClassName('dark')) {
-				element.style.display = 'none';
-			}
-			break
-		case "light":
-			document.body.classList.add("light-theme")
-			document.body.classList.remove("dark-theme")
-			for (const element of document.body.getElementsByClassName('light')) {
-				element.style.display = 'none';
-			}
-			break
-		default:
-			if (matchMedia("(prefers-color-scheme: light)").matches) {
-				document.body.classList.add("light-theme")
-				document.body.classList.remove("dark-theme")
-				for (const element of document.body.getElementsByClassName('light')) {
-					element.style.display = 'none';
-				}
-			} else {
-				document.body.classList.add("dark-theme")
-				document.body.classList.remove("light-theme")
-				for (const element of document.body.getElementsByClassName('dark')) {
-					element.style.display = 'none';
-				}
-			}
-	}
+  switch ((await utils.getOptions()).theme) {
+    case "dark":
+      document.body.classList.add("dark-theme")
+      document.body.classList.remove("light-theme")
+      for (const element of document.body.getElementsByClassName("dark")) {
+        element.style.display = "none"
+      }
+      break
+    case "light":
+      document.body.classList.add("light-theme")
+      document.body.classList.remove("dark-theme")
+      for (const element of document.body.getElementsByClassName("light")) {
+        element.style.display = "none"
+      }
+      break
+    default:
+      if (matchMedia("(prefers-color-scheme: light)").matches) {
+        document.body.classList.add("light-theme")
+        document.body.classList.remove("dark-theme")
+        for (const element of document.body.getElementsByClassName("light")) {
+          element.style.display = "none"
+        }
+      } else {
+        document.body.classList.add("dark-theme")
+        document.body.classList.remove("light-theme")
+        for (const element of document.body.getElementsByClassName("dark")) {
+          element.style.display = "none"
+        }
+      }
+  }
 }
 
 changeTheme()
 if (["ar", "iw", "ku", "fa", "ur"].includes(browser.i18n.getUILanguage())) {
-	document.getElementsByTagName("body")[0].classList.add("rtl")
-	document.getElementsByTagName("body")[0].dir = "rtl"
+  document.getElementsByTagName("body")[0].classList.add("rtl")
+  document.getElementsByTagName("body")[0].dir = "rtl"
 }
 localise.localisePage()
 
diff --git a/src/pages/options/widgets/general.js b/src/pages/options/widgets/general.js
deleted file mode 100644
index 30a8a0c8..00000000
--- a/src/pages/options/widgets/general.js
+++ /dev/null
@@ -1,216 +0,0 @@
-"use strict"
-window.browser = window.browser || window.chrome
-
-import utils from "../../../assets/javascripts/utils.js"
-import servicesHelper from "../../../assets/javascripts/services.js"
-
-const isChrome = browser.runtime.getBrowserInfo === undefined
-
-async function setOption(option, type, event) {
-	let options = await utils.getOptions()
-	switch (type) {
-		case "select":
-			options[option] = event.target.options[event.target.options.selectedIndex].value
-			break;
-		case "checkbox":
-			options[option] = event.target.checked
-			break;
-		case "range":
-			options[option] = event.target.value
-			break;
-	}
-	browser.storage.local.set({ options })
-}
-
-const exportSettingsElement = document.getElementById("export-settings")
-async function exportSettings() {
-	const options = await utils.getOptions()
-	options.version = browser.runtime.getManifest().version
-	let resultString = JSON.stringify(options, null, "  ")
-	exportSettingsElement.href = "data:application/json;base64," + btoa(resultString)
-	exportSettingsElement.download = `libredirect-settings-v${options.version}.json`
-	return
-}
-exportSettings()
-document.getElementById("general_page").onclick = exportSettings
-
-const importSettingsElement = document.getElementById("import-settings")
-const importSettingsElementText = document.getElementById("import_settings_text")
-importSettingsElement.addEventListener("change", () => {
-	function importError() {
-		const oldHTML = importSettingsElementText.innerHTML
-		importSettingsElementText.innerHTML = '<span style="color:red;">Error!</span>'
-		setTimeout(() => (importSettingsElementText.innerHTML = oldHTML), 1000)
-	}
-	importSettingsElementText.innerHTML = "..."
-	let file = importSettingsElement.files[0]
-	const reader = new FileReader()
-	reader.readAsText(file)
-	reader.onload = async () => {
-		const data = JSON.parse(reader.result)
-		if (
-			"theme" in data
-			&& data.version == browser.runtime.getManifest().version
-		) {
-			browser.storage.local.clear(async () => {
-				browser.storage.local.set({ options: data }, () => {
-					location.reload()
-				})
-			})
-		} else {
-			console.log("incompatible settings")
-			importError()
-		}
-	}
-	reader.onerror = error => {
-		console.log("error", error)
-		importError()
-	}
-})
-
-const exportSettingsSync = document.getElementById("export-settings-sync")
-const importSettingsSync = document.getElementById("import-settings-sync")
-const importSettingsSyncText = document.getElementById("import_settings_sync_text")
-
-exportSettingsSync.addEventListener("click", async () => {
-	let options = await utils.getOptions()
-	options.version = browser.runtime.getManifest().version
-	browser.storage.sync.set({ options }, () => location.reload())
-})
-
-importSettingsSync.addEventListener("click", () => {
-	function importError() {
-		importSettingsSyncText.innerHTML = '<span style="color:red;">Error!</span>'
-		setTimeout(() => (importSettingsSyncText.innerHTML = oldHTML), 1000)
-	}
-	const oldHTML = importSettingsSyncText.innerHTML
-	importSettingsSyncText.innerHTML = "..."
-	browser.storage.sync.get({ options }, r => {
-		const options = r.options
-		if (options.version == browser.runtime.getManifest().version) {
-			browser.storage.local.set({ options }, () => location.reload())
-		} else {
-			importError()
-		}
-	})
-})
-
-const resetSettings = document.getElementById("reset-settings")
-resetSettings.addEventListener("click", async () => {
-	resetSettings.innerHTML = "..."
-	await servicesHelper.initDefaults()
-	location.reload()
-})
-
-const fetchInstancesElement = document.getElementById('fetch-instances')
-fetchInstancesElement.addEventListener('change', event => {
-	setOption('fetchInstances', 'select', event)
-	location.reload()
-})
-
-const redirectOnlyInIncognitoElement = document.getElementById('redirectOnlyInIncognito')
-redirectOnlyInIncognitoElement.addEventListener('change', event => {
-	setOption('redirectOnlyInIncognito', 'checkbox', event)
-})
-
-const bookmarksMenuElement = document.getElementById('bookmarksMenu')
-bookmarksMenuElement.addEventListener('change', async event => {
-	if (event.target.checked)
-		browser.permissions.request({ permissions: ["bookmarks"] }, r => bookmarksMenuElement.checked = r)
-	else
-		browser.permissions.remove({ permissions: ["bookmarks"] }, r => bookmarksMenuElement.checked = !r)
-})
-
-let themeElement = document.getElementById("theme")
-themeElement.addEventListener("change", event => {
-	setOption("theme", "select", event)
-	location.reload()
-})
-
-let nameCustomInstanceInput = document.getElementById("exceptions-custom-instance")
-let instanceTypeElement = document.getElementById("exceptions-custom-instance-type")
-let instanceType = "url"
-
-let config = await utils.getConfig()
-
-for (const service in config.services) {
-	document.getElementById(service).addEventListener("change", async event => {
-		let options = await utils.getOptions()
-		if (event.target.checked && !options.popupServices.includes(service)) options.popupServices.push(service)
-		else if (options.popupServices.includes(service)) {
-			var index = options.popupServices.indexOf(service)
-			if (index !== -1) options.popupServices.splice(index, 1)
-		}
-		browser.storage.local.set({ options })
-	})
-}
-
-let options = await utils.getOptions()
-themeElement.value = options.theme
-fetchInstancesElement.value = options.fetchInstances
-redirectOnlyInIncognitoElement.checked = options.redirectOnlyInIncognito
-browser.permissions.contains({ permissions: ["bookmarks"] }, r => bookmarksMenuElement.checked = r)
-for (const service in config.services) document.getElementById(service).checked = options.popupServices.includes(service)
-
-instanceTypeElement.addEventListener("change", event => {
-	instanceType = event.target.options[instanceTypeElement.selectedIndex].value
-	if (instanceType == "url") {
-		nameCustomInstanceInput.setAttribute("type", "url")
-		nameCustomInstanceInput.setAttribute("placeholder", "https://www.google.com")
-	} else if (instanceType == "regex") {
-		nameCustomInstanceInput.setAttribute("type", "text")
-		nameCustomInstanceInput.setAttribute("placeholder", "https?://(www.|)youtube.com/")
-	}
-})
-
-let exceptionsCustomInstances = options.exceptions
-function calcExceptionsCustomInstances() {
-	document.getElementById("exceptions-custom-checklist").innerHTML = [...exceptionsCustomInstances.url, ...exceptionsCustomInstances.regex]
-		.map(
-			x => `<div>
-                      ${x}
-                      <button class="add" id="clear-${x}">
-                        <svg xmlns="https://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px"
-                        fill="currentColor">
-                          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
-                        </svg>
-                      </button>
-                    </div>
-                    <hr>`
-		)
-		.join("\n")
-
-	for (const x of [...exceptionsCustomInstances.url, ...exceptionsCustomInstances.regex]) {
-		document.getElementById(`clear-${x}`).addEventListener("click", async () => {
-			let index
-			index = exceptionsCustomInstances.url.indexOf(x)
-			if (index > -1) exceptionsCustomInstances.url.splice(index, 1)
-			else {
-				index = exceptionsCustomInstances.regex.indexOf(x)
-				if (index > -1) exceptionsCustomInstances.regex.splice(index, 1)
-			}
-			options = await utils.getOptions()
-			options.exceptions = exceptionsCustomInstances
-			browser.storage.local.set({ options })
-			calcExceptionsCustomInstances()
-		})
-	}
-}
-calcExceptionsCustomInstances()
-document.getElementById("custom-exceptions-instance-form").addEventListener("submit", async event => {
-	event.preventDefault()
-	let val
-	if (instanceType == "url" && nameCustomInstanceInput.validity.valid) {
-		val = nameCustomInstanceInput.value
-		if (!exceptionsCustomInstances.url.includes(val)) exceptionsCustomInstances.url.push(val)
-	} else if (instanceType == "regex") {
-		val = nameCustomInstanceInput.value
-		if (val.trim() != "" && !exceptionsCustomInstances.regex.includes(val)) exceptionsCustomInstances.regex.push(val)
-	}
-	if (val) {
-		options = await utils.getOptions()
-		options.exceptions = exceptionsCustomInstances
-		browser.storage.local.set({ options }, () => nameCustomInstanceInput.value = "")
-	}
-	calcExceptionsCustomInstances()
-})
diff --git a/src/pages/options/widgets/general.pug b/src/pages/options/widgets/general.pug
deleted file mode 100644
index 1388584a..00000000
--- a/src/pages/options/widgets/general.pug
+++ /dev/null
@@ -1,88 +0,0 @@
-section(class="block-option" id="general_page")
-    div(class="block block-option")
-        h1(data-localise="__MSG_general__") General
-    hr
-
-    div(class="block block-option")
-        label(data-localise="__MSG_theme__") Theme
-        select(id="theme" aria-label="select theme")
-            option(value="detect" data-localise="__MSG_auto__") Auto
-            option(value="light" data-localise="__MSG_light__") Light
-            option(value="dark" data-localise="__MSG_dark__") Dark
-
-    div(class="block block-option")
-        label(data-localise="__MSG_fetchPublicInstances__") Fetch public instances
-        select(id="fetch-instances" aria-label="Select fetch public instances")
-            option(value="github") GitHub
-            option(value="codeberg") Codeberg
-            option(value="disable" data-localise="__MSG_disable__") Disable
-
-    div(class="block block-option")
-        label(for='redirectOnlyInIncognito' data-localise="__MSG_redirectOnlyInIncognito__") Redirect Only in Incognito
-        input(id='redirectOnlyInIncognito' type="checkbox")
-
-    div(class="block block-option")
-        label(for='bookmarksMenu' data-localise="__MSG_bookmarksMenu__") Bookmarks menu
-        input(id='bookmarksMenu' type="checkbox")
-
-    div(class="block block-option")
-        label(data-localise="__MSG_excludeFromRedirecting__") Excluded from redirecting
-
-    form(id="custom-exceptions-instance-form")
-        div(class="block block-option")
-            div(class="block" style="padding: 0")
-                input(id="exceptions-custom-instance" placeholder="https://www.google.com" type="url" aria-label="Add url exception input")
-                |&nbsp;
-                select(id="exceptions-custom-instance-type")
-                    option(value="url") URL
-                    option(value="regex") Regex
-                |&nbsp;
-            button(class="add" id="exceptions-add-instance" type="submit" aria-label="Add the url exception")
-                svg(xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor")
-                    path(d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z")
-
-    hr
-
-    div(class="checklist" id="exceptions-custom-checklist")
-
-    div(class="buttons")
-        label(class="button button-inline" id="import_settings_text" for="import-settings")
-            svg(xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor")
-                path(d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z")
-            |&nbsp;
-            x(data-localise="__MSG_importSettings__") Import Settings
-        input(id="import-settings" type="file" accept=".json" style="display: none")
-
-        |&nbsp;&nbsp;
-
-        a(class="button button-inline" id="export-settings")
-            svg(xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor")
-                path(d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z")
-            |&nbsp;
-            x(data-localise="__MSG_exportSettings__") Export Settings
-
-        |&nbsp;&nbsp;
-
-        button(class="button button-inline" id="export-settings-sync")
-            svg(xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor")
-                path(d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z")
-            |&nbsp;
-            x() Export Settings to Sync
-
-        |&nbsp;&nbsp;
-
-        button(class="button button-inline" id="import-settings-sync")
-            svg(xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor")
-                path(d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z")
-            |&nbsp;
-            x(id="import_settings_sync_text") Import Settings from Sync
-
-        |&nbsp;&nbsp;
-
-        button(class="button button-inline" id="reset-settings")
-            svg(xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor")
-                path(d="M12,5V2L8,6l4,4V7c3.31,0,6,2.69,6,6c0,2.97-2.17,5.43-5,5.91v2.02c3.95-0.49,7-3.85,7-7.93C20,8.58,16.42,5,12,5z")
-                path(d="M6,13c0-1.65,0.67-3.15,1.76-4.24L6.34,7.34C4.9,8.79,4,10.79,4,13c0,4.08,3.05,7.44,7,7.93v-2.02 C8.17,18.43,6,15.97,6,13z")
-            x(data-localise="__MSG_resetSettings__") Reset Settings
-
-    script(type="module" src="./widgets/general.js")
\ No newline at end of file
diff --git a/src/pages/options/widgets/services.pug b/src/pages/options/widgets/services.pug
deleted file mode 100644
index e08bb001..00000000
--- a/src/pages/options/widgets/services.pug
+++ /dev/null
@@ -1,83 +0,0 @@
-each val, service in services
-    section(class="block-option" id=service+"_page")
-        div(class="block block-option")
-            h1
-                a(target="_blank" href=services[service].url)=services[service].name
-
-        hr
-
-        div(class="block block-option")
-            label(for=`${service}-enabled` data-localise="__MSG_enable__") Enable
-            input(id=`${service}-enabled` type="checkbox")
-
-        div(class="block block-option")
-            label(for=service data-localise="__MSG_showInPopup__") Show in popup
-            input(id=service type="checkbox")
-
-        div(id=service+"-opacity")
-
-            div(class="block block-option")
-                label(for=`${service}-frontend`)
-                    a(class="frontend_name" target="_blank" data-localise="__MSG_frontend__") Frontend
-                select(id=`${service}-frontend`)
-                    each val, frontend in services[service].frontends
-                        option(value=frontend)=services[service].frontends[frontend].name
-
-            div(class="block block-option" id=service+"-instance-div")
-                label(for=`${service}-instance`) Instance Type
-                select(id=`${service}-instance`)
-                    option(value="localhost") localhost
-                    option(value="public") public instances
-
-            div(class="block block-option")
-                label(for=`${service}-redirectType` data-localise="__MSG_redirectType__") Redirect Type
-                select(id=`${service}-redirectType`)
-
-
-            div(id=`${service}-embedFrontend-div` class="block block-option")
-                label(for=`${service}-embedFrontend` data-localise="__MSG_embedFrontend__") Embed Frontend
-                select(id=`${service}-embedFrontend`)
-                    each val, frontend in services[service].frontends
-                        if services[service].frontends[frontend].embeddable && services[service].frontends[frontend].instanceList
-                            option(value=frontend)=services[service].frontends[frontend].name
-
-
-            div(class="block block-option")
-                label(for=`${service}-unsupportedUrls` data-localise="__MSG_unsupportedIframesHandling__") Unsupported iframes handling
-                select(id=`${service}-unsupportedUrls`)
-                    option(value="bypass") bypass
-                    option(value="block") block
-
-            if (service == 'search')
-                div(class="block block-option")
-                    label Set LibRedirect as Default Search Engine. For how to do in chromium browsers, click <a href="https://libredirect.github.io/docs.html#search_engine_chromium">here</a>.
-
-
-            each val, frontend in services[service].frontends
-                if services[service].frontends[frontend].instanceList
-                    div(id=frontend dir="ltr")
-                        hr        
-                        div(dir="auto" class="block block-option")
-                            label(data-localise="__MSG_addYourFavoriteInstances__") Add your favorite instances
-
-                        form(class="custom-instance-form")
-                            div(class="block block-option")
-                                input(class="custom-instance" type="url" placeholder="https://instance.com" aria-label="Add instance input")
-                                button(class="add add-instance" type="submit" aria-label="Add the instance")
-                                    svg(xmlns="https://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor")
-                                        path(d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z")
-
-                        div(class="checklist custom-checklist")  
-
-                        div(class="ping block")
-                            button(class="button button-inline" id=`ping-${frontend}`)
-                                svg(xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor")
-                                    path(d="M10.45 15.5q.6.6 1.55.587.95-.012 1.4-.687L19 7l-8.4 5.6q-.675.45-.712 1.375-.038.925.562 1.525ZM12 4q1.475 0 2.838.412Q16.2 4.825 17.4 5.65l-1.9 1.2q-.825-.425-1.712-.637Q12.9 6 12 6 8.675 6 6.338 8.337 4 10.675 4 14q0 1.05.287 2.075Q4.575 17.1 5.1 18h13.8q.575-.95.838-1.975Q20 15 20 13.9q0-.9-.212-1.75-.213-.85-.638-1.65l1.2-1.9q.75 1.175 1.188 2.5.437 1.325.462 2.75.025 1.425-.325 2.725-.35 1.3-1.025 2.475-.275.45-.75.7-.475.25-1 .25H5.1q-.525 0-1-.25t-.75-.7q-.65-1.125-1-2.387Q2 15.4 2 14q0-2.075.788-3.888.787-1.812 2.15-3.175Q6.3 5.575 8.125 4.787 9.95 4 12 4Zm.175 7.825Z")
-                                |&nbsp;
-                                x() Ping instances
-
-                        each val, network in networks
-                            div(class=network)
-                                div(class="checklist")
-                                    if (network == 'clearnet')
-                                        div(class="block block-option loading") Loading...
\ No newline at end of file
diff --git a/src/pages/popup/popup.js b/src/pages/popup/popup.js
index 485ec0e0..77a43000 100644
--- a/src/pages/popup/popup.js
+++ b/src/pages/popup/popup.js
@@ -5,15 +5,15 @@ import servicesHelper from "../../assets/javascripts/services.js"
 import utils from "../../assets/javascripts/utils.js"
 
 document.getElementById("more-options").href = browser.runtime.getURL("pages/options/index.html")
-document.getElementById("more-options").setAttribute('target', '_blank')
+document.getElementById("more-options").setAttribute("target", "_blank")
 
 await browser.runtime.getPlatformInfo(r => {
-	switch (r.os) {
-		case "fuchsia":
-		case "ios":
-		case "android":
-			document.getElementsByTagName("html")[0].classList.add("mobile")
-	}
+  switch (r.os) {
+    case "fuchsia":
+    case "ios":
+    case "android":
+      document.getElementsByTagName("html")[0].classList.add("mobile")
+  }
 })
 
 const allSites = document.getElementById("all_sites")
@@ -24,94 +24,96 @@ const config = await utils.getConfig()
 const divs = {}
 
 for (const service in config.services) {
-	divs[service] = {}
-
-	divs[service].all = allSites.getElementsByClassName(service)[0]
-	divs[service].current = currSite.getElementsByClassName(service)[0]
-
-	divs[service].all_toggle = allSites.getElementsByClassName(`${service}-enabled`)[0]
-	divs[service].all_toggle.addEventListener("change", async () => {
-		const options = await utils.getOptions()
-		options[service].enabled = divs[service].all_toggle.checked
-		browser.storage.local.set({ options })
-	})
-
-	allSites.getElementsByClassName(`${service}-change_instance`)[0].addEventListener("click", () => {
-		browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-			if (tabs[0].url) {
-				const url = new URL(tabs[0].url)
-				browser.tabs.update({ url: await servicesHelper.switchInstance(url, service) })
-			}
-		})
-	})
-
-	divs[service].current_toggle = currSite.getElementsByClassName(`${service}-enabled`)[0]
-	divs[service].current_toggle.addEventListener("change", async () => {
-		const options = await utils.getOptions()
-		options[service].enabled = divs[service].current_toggle.checked
-		browser.storage.local.set({ options })
-	})
-
-	currSite.getElementsByClassName(`${service}-change_instance`)[0].addEventListener("click", () => {
-		browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-			if (tabs[0].url) {
-				const url = new URL(tabs[0].url)
-				browser.tabs.update({ url: await servicesHelper.switchInstance(url, service) })
-			}
-		})
-	})
+  divs[service] = {}
+
+  divs[service].all = allSites.getElementsByClassName(service)[0]
+  divs[service].current = currSite.getElementsByClassName(service)[0]
+
+  divs[service].all_toggle = allSites.getElementsByClassName(`${service}-enabled`)[0]
+  divs[service].all_toggle.addEventListener("change", async () => {
+    const options = await utils.getOptions()
+    options[service].enabled = divs[service].all_toggle.checked
+    browser.storage.local.set({ options })
+  })
+
+  allSites.getElementsByClassName(`${service}-change_instance`)[0].addEventListener("click", () => {
+    browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
+      if (tabs[0].url) {
+        const url = new URL(tabs[0].url)
+        browser.tabs.update({ url: await servicesHelper.switchInstance(url, service) })
+      }
+    })
+  })
+
+  divs[service].current_toggle = currSite.getElementsByClassName(`${service}-enabled`)[0]
+  divs[service].current_toggle.addEventListener("change", async () => {
+    const options = await utils.getOptions()
+    options[service].enabled = divs[service].current_toggle.checked
+    browser.storage.local.set({ options })
+  })
+
+  currSite.getElementsByClassName(`${service}-change_instance`)[0].addEventListener("click", () => {
+    browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
+      if (tabs[0].url) {
+        const url = new URL(tabs[0].url)
+        browser.tabs.update({ url: await servicesHelper.switchInstance(url, service) })
+      }
+    })
+  })
 }
 
 browser.tabs.query({ active: true, currentWindow: true }, async tabs => {
-	// Set visibility of control buttons
-	if (tabs[0].url) {
-		const hr = document.getElementById("hr")
-		var url = new URL(tabs[0].url)
-		servicesHelper.switchInstance(url).then(r => {
-			if (r) {
-				document.getElementById("change_instance_div").style.display = ""
-				hr.style.display = ""
-				document.getElementById("change_instance").addEventListener("click", async () =>
-					browser.tabs.update({ url: await servicesHelper.switchInstance(url) })
-				)
-			}
-		})
-		servicesHelper.reverse(url).then(r => {
-			if (r) {
-				hr.style.display = ""
-
-				document.getElementById("copy_original_div").style.display = ""
-				document.getElementById("copy_original").addEventListener("click", () => servicesHelper.copyRaw(url))
-
-				document.getElementById("redirect_to_original_div").style.display = ""
-				document.getElementById("redirect_to_original").addEventListener("click", () => browser.runtime.sendMessage("reverseTab"))
-			}
-		})
-		servicesHelper.redirectAsync(url, "main_frame", null, true).then(r => {
-			if (r) {
-				document.getElementById("redirect_div").style.display = ""
-				hr.style.display = ""
-				document.getElementById("redirect").addEventListener("click", () => browser.runtime.sendMessage("redirectTab"))
-			}
-		})
-	}
-
-	const options = await utils.getOptions()
-
-	// Set visibility of all service buttons
-	for (const service of options.popupServices) {
-		divs[service].all.classList.remove("hide")
-		divs[service].all_toggle.checked = options[service].enabled
-	}
-
-	// Set visibility of current page service button
-	if (url) {
-		const service = await servicesHelper.computeService(url)
-		if (service) {
-			divs[service].all.classList.add("hide")
-			divs[service].current.classList.remove("hide")
-			divs[service].current_toggle.checked = options[service].enabled
-			currentSiteDivider.style.display = ""
-		}
-	}
-})
\ No newline at end of file
+  // Set visibility of control buttons
+  if (tabs[0].url) {
+    const hr = document.getElementById("hr")
+    var url = new URL(tabs[0].url)
+    servicesHelper.switchInstance(url).then(r => {
+      if (r) {
+        document.getElementById("change_instance_div").style.display = ""
+        hr.style.display = ""
+        document
+          .getElementById("change_instance")
+          .addEventListener("click", async () => browser.tabs.update({ url: await servicesHelper.switchInstance(url) }))
+      }
+    })
+    servicesHelper.reverse(url).then(r => {
+      if (r) {
+        hr.style.display = ""
+
+        document.getElementById("copy_original_div").style.display = ""
+        document.getElementById("copy_original").addEventListener("click", () => servicesHelper.copyRaw(url))
+
+        document.getElementById("redirect_to_original_div").style.display = ""
+        document
+          .getElementById("redirect_to_original")
+          .addEventListener("click", () => browser.runtime.sendMessage("reverseTab"))
+      }
+    })
+    servicesHelper.redirectAsync(url, "main_frame", null, true).then(r => {
+      if (r) {
+        document.getElementById("redirect_div").style.display = ""
+        hr.style.display = ""
+        document.getElementById("redirect").addEventListener("click", () => browser.runtime.sendMessage("redirectTab"))
+      }
+    })
+  }
+
+  const options = await utils.getOptions()
+
+  // Set visibility of all service buttons
+  for (const service of options.popupServices) {
+    divs[service].all.classList.remove("hide")
+    divs[service].all_toggle.checked = options[service].enabled
+  }
+
+  // Set visibility of current page service button
+  if (url) {
+    const service = await servicesHelper.computeService(url)
+    if (service) {
+      divs[service].all.classList.add("hide")
+      divs[service].current.classList.remove("hide")
+      divs[service].current_toggle.checked = options[service].enabled
+      currentSiteDivider.style.display = ""
+    }
+  }
+})
diff --git a/src/pages/popup/style.css b/src/pages/popup/style.css
index 5e39b4ed..41510615 100644
--- a/src/pages/popup/style.css
+++ b/src/pages/popup/style.css
@@ -1,65 +1,65 @@
 body {
-	width: 270px;
-	min-height: auto;
+  width: 270px;
+  min-height: auto;
 }
 
 html,
 body {
-	margin: 0;
+  margin: 0;
 }
 
 .hide {
-	display: none !important;
+  display: none !important;
 }
 
 .button {
-	display: flex;
-	margin: 0 auto;
-	justify-content: space-between;
+  display: flex;
+  margin: 0 auto;
+  justify-content: space-between;
 }
 
 .button svg {
-	width: 26px;
-	height: 26px;
+  width: 26px;
+  height: 26px;
 }
 
 .bottom-button {
-	width: 100%;
+  width: 100%;
 }
 
 .space {
-	height: 10px;
+  height: 10px;
 }
 
 input {
-	height: 23px;
-	width: 46px;
+  height: 23px;
+  width: 46px;
 }
 
 div.block label {
-	margin: 0;
-	font-size: 18px;
-	font-weight: bold;
-	max-width: 180px;
+  margin: 0;
+  font-size: 18px;
+  font-weight: bold;
+  max-width: 180px;
 }
 
 div.block label:hover {
-	cursor: pointer;
+  cursor: pointer;
 }
 
 div.block div {
-	display: flex;
+  display: flex;
 }
 
 html.mobile body {
-	width: 100%;
+  width: 100%;
 }
 
 html.mobile div.block label {
-	font-size: 24px;
+  font-size: 24px;
 }
 
 html.mobile .button svg {
-	width: 30px;
-	height: 30px;
-}
\ No newline at end of file
+  width: 30px;
+  height: 30px;
+}
diff --git a/src/pages/src/App.svelte b/src/pages/src/App.svelte
new file mode 100644
index 00000000..8cceb5d1
--- /dev/null
+++ b/src/pages/src/App.svelte
@@ -0,0 +1,118 @@
+<script>
+  let browser = window.browser || window.chrome
+
+  import General from "./General/General.svelte"
+  import utils from "../../assets/javascripts/utils.js"
+  import { onDestroy } from "svelte"
+  import servicesHelper from "../../assets/javascripts/services.js"
+  import { onMount } from "svelte"
+  import Sidebar from "./Sidebar.svelte"
+  import { options, config, page } from "./stores"
+  import Services from "./Services/Services.svelte"
+
+  let _options
+  const unsubscribeOptions = options.subscribe(val => {
+    if (val) {
+      _options = val
+      browser.storage.local.set({ options: val })
+    }
+  })
+
+  let _config
+  const unsubscribeConfig = config.subscribe(val => (_config = val))
+
+  onDestroy(() => {
+    unsubscribeOptions()
+    unsubscribeConfig()
+  })
+
+  onMount(async () => {
+    let opts = await utils.getOptions()
+    if (!opts) {
+      console.log("init defulats")
+      await servicesHelper.initDefaults()
+      opts = await utils.getOptions()
+    }
+    options.set(opts)
+    config.set(await utils.getConfig())
+  })
+
+  let _page
+  page.subscribe(val => (_page = val))
+
+  const dark = {
+    text: "#fff",
+    bgMain: "#121212",
+    bgSecondary: "#202020",
+    active: "#fbc117",
+    danger: "#f04141",
+    lightGrey: "#c3c3c3",
+  }
+  const light = {
+    text: "black",
+    bgMain: "white",
+    bgSecondary: "#e4e4e4",
+    active: "#fb9817",
+    danger: "#f04141",
+    lightGrey: "#c3c3c3",
+  }
+  let cssVariables
+  $: if (_options) {
+    if (_options.theme == "dark") {
+      cssVariables = dark
+    } else if (_options.theme == "light") {
+      cssVariables = light
+    } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
+      cssVariables = dark
+    } else {
+      cssVariables = light
+    }
+  }
+</script>
+
+{#if _options && _config}
+  <div
+    class="main"
+    dir="auto"
+    style="
+    --text: {cssVariables.text};
+    --bg-main: {cssVariables.bgMain};
+    --bg-secondary: {cssVariables.bgSecondary};
+    --active: {cssVariables.active};
+    --danger: {cssVariables.danger};
+    --light-grey: {cssVariables.lightGrey};"
+  >
+    <Sidebar />
+    {#if _page == "general"}
+      <General />
+    {:else if _page == "services"}
+      <Services />
+    {/if}
+  </div>
+{:else}
+  <p>Loading...</p>
+{/if}
+
+<style>
+  :global(body) {
+    width: 100vw;
+    height: 100vh;
+    margin: 0;
+    padding: 0;
+  }
+
+  div {
+    height: 100%;
+    display: grid;
+    grid-template-columns: min-content 700px;
+    margin: 0;
+    padding-top: 50px;
+    justify-content: center;
+    font-family: "Inter";
+    box-sizing: border-box;
+    font-size: 16px;
+    background-color: var(--bg-main);
+    color: var(--text);
+    overflow: scroll;
+  }
+</style>
diff --git a/src/pages/src/General/Exceptions.svelte b/src/pages/src/General/Exceptions.svelte
new file mode 100644
index 00000000..e43afaf6
--- /dev/null
+++ b/src/pages/src/General/Exceptions.svelte
@@ -0,0 +1,108 @@
+<script>
+  import Row from "../components/Row.svelte"
+  import Select from "../components/Select.svelte"
+  import AddIcon from "../icons/AddIcon.svelte"
+  import CloseIcon from "../icons/CloseIcon.svelte"
+  import Input from "../components/Input.svelte"
+  import Label from "../components/Label.svelte"
+  import { options, config } from "../stores"
+  import { onDestroy } from "svelte"
+
+  let _options
+  let _config
+
+  const unsubscribeOptions = options.subscribe(val => (_options = val))
+  const unsubscribeConfig = config.subscribe(val => (_config = val))
+  onDestroy(() => {
+    unsubscribeOptions()
+    unsubscribeConfig()
+  })
+  let inputType = "url"
+  let inputValue = ""
+
+  $: inputPlaceholder = inputType == "url" ? "https://www.google.com" : "https?://(www.|)youtube.com/"
+
+  function removeException(exception) {
+    let index
+    index = _options.exceptions.url.indexOf(exception)
+    if (index > -1) {
+      _options.exceptions.url.splice(index, 1)
+    } else {
+      index = _options.exceptions.regex.indexOf(exception)
+      if (index > -1) _options.exceptions.regex.splice(index, 1)
+    }
+    options.set(_options)
+  }
+
+  function addException() {
+    let valid = false
+    if (inputType == "url" && /^(ftp|http|https):\/\/[^ "]+$/.test(inputValue)) {
+      valid = true
+      if (!_options.exceptions.url.includes(inputValue)) {
+        _options.exceptions.url.push(inputValue)
+      }
+    } else if (inputType == "regex") {
+      valid = true
+      if (!_options.exceptions.regex.includes(inputValue)) {
+        _options.exceptions.regex.push(inputValue)
+      }
+    }
+    if (valid) {
+      options.set(_options)
+      inputValue = ""
+    }
+  }
+</script>
+
+<div class="block block-option">
+  <Row>
+    <Label>Excluded from redirecting</Label>
+  </Row>
+  <Row>
+    <div>
+      <Input
+        placeholder={inputPlaceholder}
+        aria-label="Add url exception input"
+        bind:value={inputValue}
+        on:keydown={e => {
+          if (e.key === "Enter") addException()
+        }}
+      />
+      <Select
+        bind:value={inputType}
+        values={[
+          { value: "url", name: "URL" },
+          { value: "regex", name: "Regex" },
+        ]}
+      />
+    </div>
+    <button class="add" on:click={addException} aria-label="Add the url exception">
+      <AddIcon />
+    </button>
+  </Row>
+  <hr />
+  <div class="checklist">
+    {#each [..._options.exceptions.url, ..._options.exceptions.regex] as exception}
+      <Row>
+        {exception}
+        <button class="add" on:click={() => removeException(exception)}>
+          <CloseIcon />
+        </button>
+      </Row>
+      <hr />
+    {/each}
+  </div>
+</div>
+
+<style>
+  .add {
+    background-color: transparent;
+    border: none;
+    color: var(--text);
+    padding: 0;
+    margin: 0;
+    text-decoration: none;
+    display: inline-block;
+    cursor: pointer;
+  }
+</style>
diff --git a/src/pages/src/General/General.svelte b/src/pages/src/General/General.svelte
new file mode 100644
index 00000000..1a5e8329
--- /dev/null
+++ b/src/pages/src/General/General.svelte
@@ -0,0 +1,70 @@
+<script>
+  let browser = window.browser || window.chrome
+
+  import Exceptions from "./Exceptions.svelte"
+  import SettingsButtons from "./SettingsButtons.svelte"
+  import RowSelect from "../components/RowSelect.svelte"
+  import Checkbox from "../components/RowCheckbox.svelte"
+  import { options } from "../stores"
+  import { onDestroy } from "svelte"
+
+  let _options
+  const unsubscribe = options.subscribe(val => (_options = val))
+  onDestroy(unsubscribe)
+
+  let bookmarksPermission
+  browser.permissions.contains({ permissions: ["bookmarks"] }, r => (bookmarksPermission = r))
+  $: if (bookmarksPermission) {
+    browser.permissions.request({ permissions: ["bookmarks"] }, r => (bookmarksPermission = r))
+  } else {
+    browser.permissions.remove({ permissions: ["bookmarks"] })
+    bookmarksPermission = false
+  }
+</script>
+
+<div>
+  <RowSelect
+    label="Theme"
+    values={[
+      { value: "detect", name: "Auto" },
+      { value: "light", name: "Light" },
+      { value: "dark", name: "Dark" },
+    ]}
+    value={_options.theme}
+    onChange={e => {
+      _options["theme"] = e.target.options[e.target.options.selectedIndex].value
+      options.set(_options)
+    }}
+    ariaLabel="select theme"
+  />
+
+  <RowSelect
+    label="Fetch public instances"
+    value={_options.fetchInstances}
+    onChange={e => {
+      _options["fetchInstances"] = e.target.options[e.target.options.selectedIndex].value
+      options.set(_options)
+    }}
+    ariaLabel="Select fetch public instances"
+    values={[
+      { value: "github", name: "GitHub" },
+      { value: "codeberg", name: "Codeberg" },
+      { value: "disable", name: "Disable" },
+    ]}
+  />
+
+  <Checkbox
+    label="Redirect Only in Incognito"
+    checked={_options.redirectOnlyInIncognito}
+    onChange={e => {
+      _options["redirectOnlyInIncognito"] = e.target.checked
+      options.set(_options)
+    }}
+  />
+
+  <Checkbox label="Bookmarks menu" bind:checked={bookmarksPermission} />
+
+  <Exceptions opts={_options} />
+
+  <SettingsButtons opts={_options} />
+</div>
diff --git a/src/pages/src/General/SettingsButtons.svelte b/src/pages/src/General/SettingsButtons.svelte
new file mode 100644
index 00000000..89d5e95d
--- /dev/null
+++ b/src/pages/src/General/SettingsButtons.svelte
@@ -0,0 +1,118 @@
+<script>
+  let browser = window.browser || window.chrome
+
+  import { onDestroy } from "svelte"
+  import Button from "../components/Button.svelte"
+  import ExportIcon from "../icons/ExportIcon.svelte"
+  import ImportIcon from "../icons/ImportIcon.svelte"
+  import ResetIcon from "../icons/ResetIcon.svelte"
+  import { options } from "../stores"
+  import servicesHelper from "../../../assets/javascripts/services.js"
+  import utils from "../../../assets/javascripts/utils.js"
+
+  let _options
+  const unsubscribe = options.subscribe(val => (_options = val))
+  onDestroy(unsubscribe)
+
+  let disableButtons = false
+
+  let importSettingsInput
+  let importSettingsFiles
+  $: if (importSettingsFiles) {
+    disableButtons = true
+    const reader = new FileReader()
+    reader.readAsText(importSettingsFiles[0])
+    reader.onload = async () => {
+      const data = JSON.parse(reader.result)
+      if ("theme" in data && data.version == browser.runtime.getManifest().version) {
+        browser.storage.local.clear(async () => {
+          console.log("clearing")
+          options.set(data)
+          disableButtons = false
+        })
+      } else {
+        console.log("incompatible settings")
+        alert("Incompatible settings")
+      }
+    }
+    reader.onerror = error => {
+      console.log("error", error)
+      alert("Error!")
+    }
+  }
+
+  async function exportSettings() {
+    disableButtons = true
+    _options.version = browser.runtime.getManifest().version
+    const resultString = JSON.stringify(_options, null, "  ")
+    const anchor = document.createElement("a")
+    anchor.href = "data:application/json;base64," + btoa(resultString)
+    anchor.download = `libredirect-settings-v${_options.version}.json`
+    anchor.click()
+    disableButtons = false
+  }
+
+  async function exportSettingsSync() {
+    disableButtons = true
+    _options.version = browser.runtime.getManifest().version
+    await servicesHelper.initDefaults()
+    browser.storage.sync.set({ options: _options })
+    disableButtons = false
+  }
+
+  async function importSettingsSync() {
+    disableButtons = true
+    browser.storage.sync.get({ options }, r => {
+      const optionsSync = r.options
+      if (optionsSync.version == browser.runtime.getManifest().version) {
+        options.set(optionsSync)
+      } else {
+        alert("Error")
+      }
+      disableButtons = false
+    })
+  }
+
+  async function resetSettings() {
+    disableButtons = true
+    browser.storage.local.clear(async () => {
+      await servicesHelper.initDefaults()
+      options.set(await utils.getOptions())
+      disableButtons = false
+    })
+  }
+</script>
+
+<div class="buttons">
+  <Button on:click={() => importSettingsInput.click()} disabled={disableButtons}>
+    <ImportIcon />
+    <x data-localise="__MSG_importSettings__">Import Settings</x>
+  </Button>
+  <input
+    type="file"
+    accept=".json"
+    style="display: none"
+    bind:this={importSettingsInput}
+    bind:files={importSettingsFiles}
+  />
+
+  <Button on:click={exportSettings} disabled={disableButtons}>
+    <ExportIcon />
+    <x data-localise="__MSG_exportSettings__">Export Settings</x>
+  </Button>
+
+  <Button on:click={exportSettingsSync} disabled={disableButtons}>
+    <ExportIcon />
+    <x>Export Settings to Sync</x>
+  </Button>
+
+  <Button on:click={importSettingsSync} disabled={disableButtons}>
+    <ImportIcon />
+    <x>Import Settings from Sync</x>
+  </Button>
+
+  <Button on:click={resetSettings} disabled={disableButtons}>
+    <ResetIcon />
+    <x>Reset Settings</x>
+  </Button>
+</div>
diff --git a/src/pages/src/Services/Instances.svelte b/src/pages/src/Services/Instances.svelte
new file mode 100644
index 00000000..3c9b1ccf
--- /dev/null
+++ b/src/pages/src/Services/Instances.svelte
@@ -0,0 +1,234 @@
+<script>
+  let browser = window.browser || window.chrome
+
+  import Button from "../components/Button.svelte"
+  import AddIcon from "../icons/AddIcon.svelte"
+  import { options, config } from "../stores"
+  import PingIcon from "../icons/PingIcon.svelte"
+  import Row from "../components/Row.svelte"
+  import Input from "../components/Input.svelte"
+  import Label from "../components/Label.svelte"
+  import CloseIcon from "../icons/CloseIcon.svelte"
+  import { onDestroy, onMount } from "svelte"
+  import utils from "../../../assets/javascripts/utils"
+
+  export let selectedService
+
+  let _options
+  let _config
+
+  const unsubscribeOptions = options.subscribe(val => (_options = val))
+  const unsubscribeConfig = config.subscribe(val => (_config = val))
+  onDestroy(() => {
+    unsubscribeOptions()
+    unsubscribeConfig()
+  })
+
+  let blacklist
+  let redirects
+
+  $: serviceOptions = _options[selectedService]
+  $: serviceConf = _config.services[selectedService]
+
+  let allInstances = []
+
+  $: {
+    allInstances = []
+    if (_options[serviceOptions.frontend]) allInstances.push(..._options[serviceOptions.frontend])
+    if (redirects && redirects[serviceOptions.frontend]) {
+      for (const network in redirects[serviceOptions.frontend]) {
+        allInstances.push(...redirects[serviceOptions.frontend][network])
+      }
+    }
+  }
+
+  let pingCache
+  $: {
+    if (pingCache) browser.storage.local.set({ pingCache })
+  }
+
+  function isCustomInstance(instance) {
+    if (redirects[serviceOptions.frontend]) {
+      for (const network in redirects[serviceOptions.frontend]) {
+        if (redirects[serviceOptions.frontend][network].includes(instance)) return true
+      }
+    }
+    return false
+  }
+
+  async function pingInstances() {
+    pingCache = {}
+    for (const instance of allInstances) {
+      console.log("pinging...", instance)
+      pingCache[instance] = { color: "lightblue", value: "pinging..." }
+      const time = await utils.ping(instance)
+      pingCache[instance] = processTime(time)
+    }
+  }
+  function processTime(time) {
+    let value
+    let color
+    if (time < 5000) {
+      value = `${time}ms`
+      if (time <= 1000) color = "green"
+      else if (time <= 2000) color = "orange"
+    } else if (time >= 5000) {
+      color = "red"
+      if (time == 5000) value = "5000ms+"
+      if (time > 5000) value = `Error: ${time - 5000}`
+    } else {
+      color = "red"
+      value = "Server not found"
+    }
+    return { color, value }
+  }
+
+  onMount(async () => {
+    blacklist = await utils.getBlacklist(_options)
+    redirects = await utils.getList(_options)
+    pingCache = await utils.getPingCache()
+  })
+
+  let addInstanceValue
+  function addInstance() {
+    const instance = utils.protocolHost(new URL(addInstanceValue))
+    if (!_options[serviceOptions.frontend].includes(instance)) {
+      _options[serviceOptions.frontend].push(instance)
+      addInstanceValue = ""
+      options.set(_options)
+    }
+  }
+</script>
+
+{#if serviceConf.frontends[serviceOptions.frontend].instanceList && redirects && blacklist}
+  <hr />
+  <div dir="ltr">
+    <div class="ping">
+      <Button on:click={pingInstances}>
+        <PingIcon />
+        Ping Instances
+      </Button>
+    </div>
+
+    <Row>
+      <Label>Add your favorite instances</Label>
+    </Row>
+
+    <Row>
+      <Input
+        bind:value={addInstanceValue}
+        type="url"
+        placeholder="https://instance.com"
+        aria-label="Add instance input"
+        on:keydown={e => {
+          if (e.key === "Enter") addInstance()
+        }}
+      />
+      <button on:click={addInstance} class="add" aria-label="Add the instance">
+        <AddIcon />
+      </button>
+    </Row>
+
+    {#each _options[serviceOptions.frontend] as instance}
+      <Row>
+        <span>
+          <a href={instance} target="_blank" rel="noopener noreferrer">{instance}</a>
+          {#if isCustomInstance(instance)}
+            <span style="color:grey">custom</span>
+          {/if}
+          {#if pingCache && pingCache[instance]}
+            <span style="color:{pingCache[instance].color}">{pingCache[instance].value}</span>
+          {/if}
+        </span>
+        <button
+          class="add"
+          aria-label="Remove Instance"
+          on:click={() => {
+            const index = _options[serviceOptions.frontend].indexOf(instance)
+            if (index > -1) {
+              _options[serviceOptions.frontend].splice(index, 1)
+              options.set(_options)
+            }
+          }}
+        >
+          <CloseIcon />
+        </button>
+      </Row>
+      <hr />
+    {/each}
+    <Row></Row>
+
+    {#if redirects !== "disabled" && blacklist !== "disabled"}
+      {#if redirects[serviceOptions.frontend] && redirects[serviceOptions.frontend]["clearnet"]}
+        {#each Object.entries(_config.networks) as [networkName, network]}
+          {#if redirects[serviceOptions.frontend] && redirects[serviceOptions.frontend][networkName]}
+            <Row><Label>{network.name}</Label></Row>
+            <hr />
+            {#each redirects[serviceOptions.frontend][networkName] as instance}
+              <Row>
+                <span>
+                  <a href={instance} target="_blank" rel="noopener noreferrer">{instance}</a>
+                  {#if blacklist.cloudflare.includes(instance)}
+                    <a
+                      href="https://libredirect.github.io/docs.html#instances"
+                      target="_blank"
+                      rel="noopener noreferrer"
+                      style="color:red;"
+                    >
+                      cloudflare
+                    </a>
+                  {/if}
+                  {#if _options[serviceOptions.frontend].includes(instance)}
+                    <span style="color:grey">chosen</span>
+                  {/if}
+                  {#if pingCache && pingCache[instance]}
+                    <span style="color:{pingCache[instance].color}">{pingCache[instance].value}</span>
+                  {/if}
+                </span>
+                <button
+                  class="add"
+                  aria-label="Add instance"
+                  on:click={() => {
+                    if (_options[serviceOptions.frontend]) {
+                      if (!_options[serviceOptions.frontend].includes(instance)) {
+                        _options[serviceOptions.frontend].push(instance)
+                        options.set(_options)
+                      }
+                    }
+                  }}
+                >
+                  <AddIcon />
+                </button>
+              </Row>
+              <hr />
+            {/each}
+          {/if}
+        {/each}
+      {:else}
+        <Row><Label>No instances found.</Label></Row>
+      {/if}
+    {/if}
+  </div>
+{/if}
+
+<style>
+  .add {
+    background-color: transparent;
+    border: none;
+    color: var(--text);
+    padding: 0;
+    margin: 0;
+    text-decoration: none;
+    display: inline-block;
+    cursor: pointer;
+  }
+
+  a {
+    color: var(--text);
+    text-decoration: none;
+  }
+
+  a:hover {
+    text-decoration: underline;
+  }
+</style>
diff --git a/src/pages/src/Services/RedirectType.svelte b/src/pages/src/Services/RedirectType.svelte
new file mode 100644
index 00000000..92b8c6d1
--- /dev/null
+++ b/src/pages/src/Services/RedirectType.svelte
@@ -0,0 +1,84 @@
+<script>
+  import { onDestroy } from "svelte"
+
+  import RowSelect from "../components/RowSelect.svelte"
+  import { options, config } from "../stores"
+
+  let _options
+  let _config
+
+  const unsubscribeOptions = options.subscribe(val => (_options = val))
+  const unsubscribeConfig = config.subscribe(val => (_config = val))
+  onDestroy(() => {
+    unsubscribeOptions()
+    unsubscribeConfig()
+  })
+
+  export let selectedService
+
+  $: serviceConf = _config.services[selectedService]
+  $: serviceOptions = _options[selectedService]
+  $: frontendName = _options[selectedService].frontend
+
+  let values
+  $: if (serviceConf.frontends[frontendName].embeddable) {
+    values = [
+      { value: "both", name: "Both" },
+      { value: "sub_frame", name: "Only Embedded" },
+      { value: "main_frame", name: "Only Not Embedded" },
+    ]
+  } else if (
+    serviceConf.frontends[frontendName].desktopApp &&
+    Object.values(serviceConf.frontends).some(frontend => frontend.embeddable)
+  ) {
+    values = [
+      { value: "both", name: "both" },
+      { value: "main_frame", name: "Only Not Embedded" },
+    ]
+    if (serviceOptions.redirectType == "sub_frame") {
+      serviceOptions.redirectType = "main_frame"
+      options.set(_options)
+    }
+  } else {
+    values = [{ value: "main_frame", name: "Only Not Embedded" }]
+    serviceOptions.redirectType = "main_frame"
+    options.set(_options)
+  }
+
+  let embeddableFrontends = []
+  $: (() => {
+    if (serviceConf) {
+      embeddableFrontends = []
+      for (const [frontendId, frontendConf] of Object.entries(serviceConf.frontends)) {
+        if (frontendConf.embeddable && frontendConf.instanceList) {
+          embeddableFrontends.push({
+            value: frontendId,
+            name: frontendConf.name,
+          })
+        }
+      }
+    }
+  })()
+</script>
+
+<RowSelect
+  label="Redirect Type"
+  value={serviceOptions.redirectType}
+  onChange={e => {
+    serviceOptions.redirectType = e.target.options[e.target.options.selectedIndex].value
+    options.set(_options)
+  }}
+  {values}
+/>
+
+{#if serviceConf.frontends[frontendName].desktopApp && serviceOptions.redirectType != "main_frame"}
+  <RowSelect
+    label="Embed Frontend"
+    value={_options.embedFrontend}
+    onChange={e => {
+      serviceOptions.embedFrontend = e.target.options[e.target.options.selectedIndex].value
+      options.set(_options)
+    }}
+    values={embeddableFrontends}
+  />
+{/if}
diff --git a/src/pages/src/Services/Services.svelte b/src/pages/src/Services/Services.svelte
new file mode 100644
index 00000000..b5d9285c
--- /dev/null
+++ b/src/pages/src/Services/Services.svelte
@@ -0,0 +1,119 @@
+<script>
+  let browser = window.browser || window.chrome
+
+  import Checkbox from "../components/RowCheckbox.svelte"
+  import RowSelect from "../components/RowSelect.svelte"
+  import Row from "../components/Row.svelte"
+  import Label from "../components/Label.svelte"
+  import Select from "../components/Select.svelte"
+  import { options, config } from "../stores"
+  import RedirectType from "./RedirectType.svelte"
+  import { onDestroy, onMount } from "svelte"
+  import Instances from "./Instances.svelte"
+
+  let _options
+  let _config
+
+  const unsubscribeOptions = options.subscribe(val => (_options = val))
+  const unsubscribeConfig = config.subscribe(val => (_config = val))
+  onDestroy(() => {
+    unsubscribeOptions()
+    unsubscribeConfig()
+  })
+
+  let selectedService = "youtube"
+
+  $: serviceConf = _config.services[selectedService]
+  $: serviceOptions = _options[selectedService]
+</script>
+
+<div>
+  <Row>
+    <Label>
+      Service:
+      <a href={serviceConf.url} target="_blank" rel="noopener noreferrer">{serviceConf.url}</a>
+    </Label>
+    <Select
+      value={selectedService}
+      values={[
+        ...Object.entries(_config.services).map(([serviceId, service]) => {
+          return { value: serviceId, name: service.name }
+        }),
+      ]}
+      onChange={e => (selectedService = e.target.options[e.target.options.selectedIndex].value)}
+    />
+  </Row>
+
+  <hr />
+
+  <Checkbox
+    label="Enable"
+    checked={serviceOptions.enabled}
+    onChange={e => {
+      serviceOptions.enabled = e.target.checked
+      options.set(_options)
+    }}
+  />
+
+  <div style={!serviceOptions.enabled && "pointer-events: none;opacity: 0.4;user-select: none;"}>
+    <Checkbox
+      label="Show in popup"
+      checked={_options.popupServices.includes(selectedService)}
+      onChange={e => {
+        if (e.target.checked && !_options.popupServices.includes(selectedService)) {
+          _options.popupServices.push(selectedService)
+        } else if (_options.popupServices.includes(selectedService)) {
+          const index = _options.popupServices.indexOf(selectedService)
+          if (index !== -1) _options.popupServices.splice(index, 1)
+        }
+        options.set(_options)
+      }}
+    />
+
+    <Row>
+      <Label>
+        Frontend:
+        <a href={serviceConf.frontends[serviceOptions.frontend].url} target="_blank" rel="noopener noreferrer"
+          >{serviceConf.frontends[serviceOptions.frontend].url}</a
+        >
+      </Label>
+      <Select
+        value={serviceOptions.frontend}
+        values={[
+          ...Object.entries(serviceConf.frontends).map(([frontendId, frontend]) => ({
+            value: frontendId,
+            name: frontend.name,
+          })),
+        ]}
+        onChange={e => {
+          serviceOptions.frontend = e.target.options[e.target.options.selectedIndex].value
+          options.set(_options)
+        }}
+      />
+    </Row>
+
+    <RedirectType {selectedService} />
+
+    <RowSelect
+      label="Unsupported iframes handling"
+      value={serviceOptions.unsupportedUrls}
+      onChange={e => {
+        serviceOptions.unsupportedUrls = e.target.options[e.target.options.selectedIndex].value
+        options.set(_options)
+      }}
+      values={[
+        { value: "bypass", name: "Bypass" },
+        { value: "block", name: "Block" },
+      ]}
+    />
+
+    {#if selectedService == "search"}
+      <div>
+        Set LibRedirect as Default Search Engine. For how to do in chromium browsers, click
+        <a href="https://libredirect.github.io/docs.html#search_engine_chromium">here</a>.
+      </div>
+    {/if}
+
+    <Instances {selectedService} />
+  </div>
+</div>
diff --git a/src/pages/src/Sidebar.svelte b/src/pages/src/Sidebar.svelte
new file mode 100644
index 00000000..be59a731
--- /dev/null
+++ b/src/pages/src/Sidebar.svelte
@@ -0,0 +1,43 @@
+<script>
+  import { page } from "./stores"
+  import GeneralIcon from "./icons/GeneralIcon.svelte"
+  import ServicesIcon from "./icons/ServicesIcon.svelte"
+  import AboutIcon from "./icons/AboutIcon.svelte"
+</script>
+
+<div>
+  <a href="#general" on:click={() => page.set("general")} style={$page == "general" && "color: var(--active);"}>
+    <GeneralIcon style="margin-right: 5px" />
+    <span data-localise="__MSG_general__">General</span>
+  </a>
+  <a href="#services" on:click={() => page.set("services")} style={$page == "services" && "color: var(--active);"}>
+    <ServicesIcon style="margin-right: 5px" />
+    <span data-localise="__MSG_general__">Services</span>
+  </a>
+  <a href="https://libredirect.github.io" target="_blank" rel="noopener noreferrer">
+    <AboutIcon style="margin-right: 5px" />
+    <span data-localise="__MSG_about__">About</span>
+  </a>
+</div>
+
+<style>
+  div {
+    display: flex;
+    flex-direction: column;
+    margin: 0 20px;
+  }
+
+  a {
+    display: flex;
+    align-items: center;
+    font-size: 18px;
+    text-decoration: none;
+    color: var(--text);
+    transition: 0.1s;
+    margin: 10px;
+  }
+
+  a:hover {
+    color: var(--active);
+  }
+</style>
diff --git a/src/pages/src/components/Button.svelte b/src/pages/src/components/Button.svelte
new file mode 100644
index 00000000..3405c2ea
--- /dev/null
+++ b/src/pages/src/components/Button.svelte
@@ -0,0 +1,29 @@
+<button on:click {...$$props}>
+  <slot></slot>
+</button>
+
+<style>
+  button {
+    color: var(--text);
+    border: none;
+    text-decoration: none;
+    cursor: pointer;
+    font-size: 16px;
+    font-weight: bold;
+    transition-duration: 0.1s;
+    display: inline-flex;
+    align-items: center;
+    margin: 7.5px 0;
+    background-color: var(--bg-secondary);
+    border-radius: 5px;
+    padding: 10px;
+  }
+
+  button:hover {
+    color: var(--active);
+  }
+
+  button:active {
+    transform: translateY(1px);
+  }
+</style>
diff --git a/src/pages/src/components/Checkbox.svelte b/src/pages/src/components/Checkbox.svelte
new file mode 100644
index 00000000..f5245045
--- /dev/null
+++ b/src/pages/src/components/Checkbox.svelte
@@ -0,0 +1,52 @@
+<script>
+  export let checked
+  export let onChange
+</script>
+
+<input bind:checked on:change={onChange} type="checkbox" />
+
+<style>
+  input[type="checkbox"] {
+    appearance: none;
+    -moz-appearance: none;
+    -webkit-appearance: none;
+    width: 46px;
+    height: 24px;
+    background-color: var(--light-grey);
+    border-radius: 50px;
+    transition: 0.4s;
+    cursor: pointer;
+  }
+
+  input[type="checkbox"]:checked {
+    background-color: var(--active);
+  }
+
+  input[type="checkbox"]::before {
+    content: "";
+    display: inline-block;
+    width: 18px;
+    height: 18px;
+    box-sizing: border-box;
+    position: relative;
+    top: 3px;
+    left: 3.5px;
+    background-color: white;
+    border-radius: 50%;
+    transition: 0.3s;
+  }
+
+  input[type="checkbox"]:checked::before {
+    left: 24px;
+  }
+
+  /* body.rtl div.block input[type="checkbox"]::before {
+      left: auto;
+      right: 4px;
+    } */
+
+  /* body.rtl div.block input[type="checkbox"]:checked::before {
+      left: auto;
+      right: 24px;
+    } */
+</style>
diff --git a/src/pages/src/components/Input.svelte b/src/pages/src/components/Input.svelte
new file mode 100644
index 00000000..d963233c
--- /dev/null
+++ b/src/pages/src/components/Input.svelte
@@ -0,0 +1,41 @@
+<script>
+  export let value
+</script>
+
+<input
+  {...$$props}
+  bind:value
+  on:blur
+  on:change
+  on:click
+  on:contextmenu
+  on:focus
+  on:keydown
+  on:keypress
+  on:keyup
+  on:mouseover
+  on:mouseenter
+  on:mouseleave
+  on:paste
+  on:input
+/>
+
+<style>
+  input {
+    font-weight: bold;
+    box-sizing: border-box;
+    color: var(--text);
+    font-size: 16px;
+    padding: 8px;
+    background-color: var(--bg-secondary);
+    border: none;
+    margin: 0;
+    width: 400px;
+    border-radius: 3px;
+    outline-color: var(--active);
+  }
+
+  input:focus {
+    outline-color: var(--active);
+  }
+</style>
diff --git a/src/pages/src/components/Label.svelte b/src/pages/src/components/Label.svelte
new file mode 100644
index 00000000..39930cd1
--- /dev/null
+++ b/src/pages/src/components/Label.svelte
@@ -0,0 +1,18 @@
+<span>
+  <slot></slot>
+</span>
+
+<style>
+  span {
+    font-size: 18px;
+  }
+
+  span :global(a) {
+    color: var(--text);
+    text-decoration: none;
+  }
+
+  span :global(a:hover) {
+    text-decoration: underline;
+  }
+</style>
diff --git a/src/pages/src/components/Row.svelte b/src/pages/src/components/Row.svelte
new file mode 100644
index 00000000..4f23cffa
--- /dev/null
+++ b/src/pages/src/components/Row.svelte
@@ -0,0 +1,12 @@
+<div {...$$props}>
+  <slot></slot>
+</div>
+
+<style>
+  div {
+    justify-content: space-between;
+    display: flex;
+    align-items: center;
+    margin: 20px 0;
+  }
+</style>
diff --git a/src/pages/src/components/RowCheckbox.svelte b/src/pages/src/components/RowCheckbox.svelte
new file mode 100644
index 00000000..b7ccab93
--- /dev/null
+++ b/src/pages/src/components/RowCheckbox.svelte
@@ -0,0 +1,14 @@
+<script>
+  import Row from "./Row.svelte"
+  import Checkbox from "./Checkbox.svelte"
+  import Label from "./Label.svelte"
+
+  export let label
+  export let checked
+  export let onChange
+</script>
+
+<Row>
+  <Label>{label}</Label>
+  <Checkbox bind:checked {onChange} />
+</Row>
diff --git a/src/pages/src/components/RowSelect.svelte b/src/pages/src/components/RowSelect.svelte
new file mode 100644
index 00000000..d685803e
--- /dev/null
+++ b/src/pages/src/components/RowSelect.svelte
@@ -0,0 +1,19 @@
+<script>
+  import Row from "./Row.svelte"
+  import Select from "./Select.svelte"
+  import Label from "./Label.svelte"
+
+  export let label
+  export let values
+  export let value
+  export let onChange
+  export let ariaLabel
+</script>
+
+<Row>
+  <Label>{label}</Label>
+  <Select {value} {values} {onChange} {ariaLabel} />
+</Row>
+
+<style>
+</style>
diff --git a/src/pages/src/components/Select.svelte b/src/pages/src/components/Select.svelte
new file mode 100644
index 00000000..a0939d1f
--- /dev/null
+++ b/src/pages/src/components/Select.svelte
@@ -0,0 +1,34 @@
+<script>
+  export let values
+  export let value
+  export let onChange
+  export let ariaLabel
+</script>
+
+<select  bind:value={value} on:change={onChange} aria-label={ariaLabel} on:change on:contextmenu on:input>
+  {#each values as option}
+    <option value={option.value}>{option.name}</option>
+  {/each}
+</select>
+
+<style>
+  select {
+    font-weight: bold;
+    box-sizing: border-box;
+    border-style: solid;
+    border-color: #767676;
+    color: var(--text);
+    font-size: 16px;
+    padding: 8px;
+    background-color: var(--bg-secondary);
+    border: none;
+    margin: 0;
+    max-width: 500px;
+    border-radius: 3px;
+  }
+
+  select:disabled {
+    opacity: 0.6;
+    cursor: not-allowed;
+  }
+</style>
diff --git a/src/pages/src/icons/AboutIcon.svelte b/src/pages/src/icons/AboutIcon.svelte
new file mode 100644
index 00000000..e113dd68
--- /dev/null
+++ b/src/pages/src/icons/AboutIcon.svelte
@@ -0,0 +1,11 @@
+<svg
+  {...$$props}
+  xmlns="http://www.w3.org/2000/svg"
+  height="24px"
+  viewBox="0 -960 960 960"
+  width="24px"
+  fill="currentColor"
+  ><path
+    d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"
+  /></svg
+>
diff --git a/src/pages/src/icons/AddIcon.svelte b/src/pages/src/icons/AddIcon.svelte
new file mode 100644
index 00000000..ab26f078
--- /dev/null
+++ b/src/pages/src/icons/AddIcon.svelte
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor">
+  <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
+</svg>
diff --git a/src/pages/src/icons/CloseIcon.svelte b/src/pages/src/icons/CloseIcon.svelte
new file mode 100644
index 00000000..ddfb29cb
--- /dev/null
+++ b/src/pages/src/icons/CloseIcon.svelte
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="currentColor"
+  ><path d="m291-240-51-51 189-189-189-189 51-51 189 189 189-189 51 51-189 189 189 189-51 51-189-189-189 189Z" /></svg
+>
diff --git a/src/pages/src/icons/ExportIcon.svelte b/src/pages/src/icons/ExportIcon.svelte
new file mode 100644
index 00000000..196726a8
--- /dev/null
+++ b/src/pages/src/icons/ExportIcon.svelte
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor">
+  <path
+    d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
+  />
+</svg>
diff --git a/src/pages/src/icons/GeneralIcon.svelte b/src/pages/src/icons/GeneralIcon.svelte
new file mode 100644
index 00000000..b9429021
--- /dev/null
+++ b/src/pages/src/icons/GeneralIcon.svelte
@@ -0,0 +1,13 @@
+<svg
+  {...$$props}
+  xmlns="http://www.w3.org/2000/svg"
+  enable-background="new 0 0 24 24"
+  height="26px"
+  viewBox="0 0 24 24"
+  width="26px"
+  fill="currentColor"
+>
+  <path
+    d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"
+  ></path>
+</svg>
diff --git a/src/pages/src/icons/ImportIcon.svelte b/src/pages/src/icons/ImportIcon.svelte
new file mode 100644
index 00000000..f022b4f0
--- /dev/null
+++ b/src/pages/src/icons/ImportIcon.svelte
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor">
+    <path
+      d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
+    />
+  </svg>
\ No newline at end of file
diff --git a/src/pages/src/icons/PingIcon.svelte b/src/pages/src/icons/PingIcon.svelte
new file mode 100644
index 00000000..8fcfe27b
--- /dev/null
+++ b/src/pages/src/icons/PingIcon.svelte
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px" fill="currentColor">
+  <path
+    d="M10.45 15.5q.6.6 1.55.587.95-.012 1.4-.687L19 7l-8.4 5.6q-.675.45-.712 1.375-.038.925.562 1.525ZM12 4q1.475 0 2.838.412Q16.2 4.825 17.4 5.65l-1.9 1.2q-.825-.425-1.712-.637Q12.9 6 12 6 8.675 6 6.338 8.337 4 10.675 4 14q0 1.05.287 2.075Q4.575 17.1 5.1 18h13.8q.575-.95.838-1.975Q20 15 20 13.9q0-.9-.212-1.75-.213-.85-.638-1.65l1.2-1.9q.75 1.175 1.188 2.5.437 1.325.462 2.75.025 1.425-.325 2.725-.35 1.3-1.025 2.475-.275.45-.75.7-.475.25-1 .25H5.1q-.525 0-1-.25t-.75-.7q-.65-1.125-1-2.387Q2 15.4 2 14q0-2.075.788-3.888.787-1.812 2.15-3.175Q6.3 5.575 8.125 4.787 9.95 4 12 4Zm.175 7.825Z"
+  /></svg
+>
diff --git a/src/pages/src/icons/ResetIcon.svelte b/src/pages/src/icons/ResetIcon.svelte
new file mode 100644
index 00000000..0900aef8
--- /dev/null
+++ b/src/pages/src/icons/ResetIcon.svelte
@@ -0,0 +1,15 @@
+<svg
+  xmlns="http://www.w3.org/2000/svg"
+  enable-background="new 0 0 24 24"
+  height="24px"
+  viewBox="0 0 24 24"
+  width="24px"
+  fill="currentColor"
+>
+  <path
+    d="M12,5V2L8,6l4,4V7c3.31,0,6,2.69,6,6c0,2.97-2.17,5.43-5,5.91v2.02c3.95-0.49,7-3.85,7-7.93C20,8.58,16.42,5,12,5z"
+  />
+  <path
+    d="M6,13c0-1.65,0.67-3.15,1.76-4.24L6.34,7.34C4.9,8.79,4,10.79,4,13c0,4.08,3.05,7.44,7,7.93v-2.02 C8.17,18.43,6,15.97,6,13z"
+  />
+</svg>
diff --git a/src/pages/src/icons/ServicesIcon.svelte b/src/pages/src/icons/ServicesIcon.svelte
new file mode 100644
index 00000000..ec24259b
--- /dev/null
+++ b/src/pages/src/icons/ServicesIcon.svelte
@@ -0,0 +1,11 @@
+<svg
+  {...$$props}
+  xmlns="http://www.w3.org/2000/svg"
+  fill="currentColor"
+  height="24px"
+  viewBox="0 -960 960 960"
+  width="24px"
+  ><path
+    d="m240-120 240-240 240 240H240ZM80-280v-480q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v480q0 33-23.5 56.5T800-200H680v-80h120v-480H160v480h120v80H160q-33 0-56.5-23.5T80-280Zm400-200Z"
+  /></svg
+>
diff --git a/src/pages/src/main.js b/src/pages/src/main.js
new file mode 100644
index 00000000..c4012f4a
--- /dev/null
+++ b/src/pages/src/main.js
@@ -0,0 +1,7 @@
+import App from "./App.svelte"
+
+const app = new App({
+  target: document.body,
+})
+
+export default app
diff --git a/src/pages/src/stores.js b/src/pages/src/stores.js
new file mode 100644
index 00000000..782f6064
--- /dev/null
+++ b/src/pages/src/stores.js
@@ -0,0 +1,5 @@
+import { writable } from "svelte/store"
+
+export const options = writable(null)
+export const config = writable(null)
+export const page = writable("general")
diff --git a/src/pages/stylesheets/styles.css b/src/pages/stylesheets/styles.css
index b220efdb..ca6f0dca 100644
--- a/src/pages/stylesheets/styles.css
+++ b/src/pages/stylesheets/styles.css
@@ -1,450 +1,449 @@
 body {
-	--text: #fff;
-	--bg-main: #121212;
-	--bg-secondary: #202020;
-	--active: #fbc117;
-	--danger: #f04141;
-	--light-grey: #c3c3c3;
+  --text: #fff;
+  --bg-main: #121212;
+  --bg-secondary: #202020;
+  --active: #fbc117;
+  --danger: #f04141;
+  --light-grey: #c3c3c3;
 }
 
 @font-face {
-	font-family: "Inter";
-	src: url("Inter-VariableFont_slnt,wght.ttf");
-	font-weight: normal;
-	font-style: normal;
+  font-family: "Inter";
+  src: url("Inter-VariableFont_slnt,wght.ttf");
+  font-weight: normal;
+  font-style: normal;
 }
 
 @font-face {
-	font-family: "Vazirmatn";
-	src: url("Vazirmatn-VariableFont_wght.ttf");
-	font-weight: normal;
-	font-style: normal;
+  font-family: "Vazirmatn";
+  src: url("Vazirmatn-VariableFont_wght.ttf");
+  font-weight: normal;
+  font-style: normal;
 }
 
 body {
-	margin: auto;
-	padding: 0;
-	font-family: "Inter";
-	font-size: 16px;
-	background-color: var(--bg-main);
-	color: var(--text);
+  margin: auto;
+  padding: 0;
+  font-family: "Inter";
+  font-size: 16px;
+  background-color: var(--bg-main);
+  color: var(--text);
 }
 
 body * {
-	font-family: "Inter";
+  font-family: "Inter";
 }
 
 body.rtl {
-	font-family: "Vazirmatn";
+  font-family: "Vazirmatn";
 }
 
 body.rtl * {
-	font-family: "Vazirmatn";
+  font-family: "Vazirmatn";
 }
 
 div.block input[type="checkbox"] {
-	appearance: none;
-	-moz-appearance: none;
-	-webkit-appearance: none;
+  appearance: none;
+  -moz-appearance: none;
+  -webkit-appearance: none;
 }
 
 .title {
-	display: flex;
-	align-items: center;
-	text-decoration: none;
-	width: min-content;
-	color: var(--text);
-	transition: .1s;
+  display: flex;
+  align-items: center;
+  text-decoration: none;
+  width: min-content;
+  color: var(--text);
+  transition: 0.1s;
 }
 
 .title:hover {
-	opacity: 1 !important;
+  opacity: 1 !important;
 }
 
 .title:hover a {
-	color: var(--active);
+  color: var(--active);
 }
 
 img,
 svg {
-	margin-right: 10px;
-	height: 26px;
-	width: 26px;
-	color: var(--text);
+  margin-right: 10px;
+  height: 26px;
+  width: 26px;
+  color: var(--text);
 }
 
 body.rtl img,
 body.rtl svg {
-	margin-right: 0px;
-	margin-left: 10px;
+  margin-right: 0px;
+  margin-left: 10px;
 }
 
 input[type="url"],
 input[type="text"],
 select {
-	font-weight: bold;
-	box-sizing: border-box;
-	border-style: solid;
-	border-color: #767676;
-	color: var(--text);
-	font-size: 16px;
-	padding: 8px;
-	background-color: var(--bg-secondary);
-	border: none;
-	margin: 0;
-	max-width: 500px;
-	border-radius: 3px;
+  font-weight: bold;
+  box-sizing: border-box;
+  border-style: solid;
+  border-color: #767676;
+  color: var(--text);
+  font-size: 16px;
+  padding: 8px;
+  background-color: var(--bg-secondary);
+  border: none;
+  margin: 0;
+  max-width: 500px;
+  border-radius: 3px;
 }
 
 input[type="url"],
 input[type="text"] {
-	width: 400px;
-	cursor: text;
+  width: 400px;
+  cursor: text;
 }
 
 input:invalid {
-	color: var(--danger);
+  color: var(--danger);
 }
 
 .button svg {
-	height: 18px;
-	width: 18px;
+  height: 18px;
+  width: 18px;
 }
 
 section.block-option {
-	width: 750px;
-	margin: 0 50px;
+  width: 750px;
+  margin: 0 50px;
 }
 
 section.block-option h2 {
-	margin: 0;
+  margin: 0;
 }
 
 body.option {
-	display: flex;
-	padding: 40px;
-	width: 1160px;
+  display: flex;
+  padding: 40px;
+  width: 1160px;
 }
 
 section.links {
-	display: flex;
-	flex-wrap: wrap;
-	flex-direction: column;
-	width: 350px;
-	max-height: 1030px;
+  display: flex;
+  flex-wrap: wrap;
+  flex-direction: column;
+  width: 350px;
+  max-height: 1030px;
 }
 
 section.links div {
-	margin: 10px;
-	width: max-content;
+  margin: 10px;
+  width: max-content;
 }
 
 a {
-	text-decoration: none;
-	color: var(--text);
-	transition: 0.1s;
+  text-decoration: none;
+  color: var(--text);
+  transition: 0.1s;
 }
 
 a:hover {
-	color: var(--active);
+  color: var(--active);
 }
 
 section.links a {
-	display: flex;
-	align-items: center;
-	font-size: 18px;
-	text-decoration: none;
-	color: white;
-	transition: 0.1s;
+  display: flex;
+  align-items: center;
+  font-size: 18px;
+  text-decoration: none;
+  color: white;
+  transition: 0.1s;
 }
 
 section.links a:hover,
 section.links .selected {
-	opacity: 1 !important;
+  opacity: 1 !important;
 }
 
 section.links .selected a {
-	color: var(--active);
+  color: var(--active);
 }
 
 ::placeholder {
-	color: var(--text);
-	opacity: 0.7;
+  color: var(--text);
+  opacity: 0.7;
 }
 
 hr {
-	height: 2px;
-	margin: 0 15px;
-	background-color: rgb(77, 77, 77);
-	border: none;
+  height: 2px;
+  margin: 0 15px;
+  background-color: rgb(77, 77, 77);
+  border: none;
 }
 
 div.block {
-	padding: 0 15px;
-	justify-content: space-between;
-	display: flex;
-	align-items: center;
-	margin-top: 10px;
-	margin-bottom: 10px;
+  padding: 0 15px;
+  justify-content: space-between;
+  display: flex;
+  align-items: center;
+  margin-top: 10px;
+  margin-bottom: 10px;
 }
 
 div.block-option {
-	margin: 30px 0;
+  margin: 30px 0;
 }
 
 div.block-option label {
-	margin-right: 5px;
-	width: 80%;
-	min-width: 150px;
-	font-size: 18px;
+  margin-right: 5px;
+  width: 80%;
+  min-width: 150px;
+  font-size: 18px;
 }
 
 div.block-option h1 {
-	margin: 0;
-	font-size: 28px;
-	color: var(--text);
+  margin: 0;
+  font-size: 28px;
+  color: var(--text);
 }
 
 div.block-option div {
-	text-align: center;
+  text-align: center;
 }
 
 div.block input[type="checkbox"] {
-	width: 46px;
-	height: 24px;
-	background-color: var(--light-grey);
-	border-radius: 50px;
-	transition: 0.4s;
-	cursor: pointer;
+  width: 46px;
+  height: 24px;
+  background-color: var(--light-grey);
+  border-radius: 50px;
+  transition: 0.4s;
+  cursor: pointer;
 }
 
 div.block input[type="checkbox"]:checked {
-	background-color: var(--active);
+  background-color: var(--active);
 }
 
 div.block input[type="checkbox"]::before {
-	content: "";
-	display: inline-block;
-	width: 18px;
-	height: 18px;
-	box-sizing: border-box;
-	position: relative;
-	top: 2.5px;
-	left: 3.5px;
-	background-color: white;
-	border-radius: 50%;
-	transition: 0.3s;
+  content: "";
+  display: inline-block;
+  width: 18px;
+  height: 18px;
+  box-sizing: border-box;
+  position: relative;
+  top: 2.5px;
+  left: 3.5px;
+  background-color: white;
+  border-radius: 50%;
+  transition: 0.3s;
 }
 
 body.rtl div.block input[type="checkbox"]::before {
-	left: auto;
-	right: 4px;
+  left: auto;
+  right: 4px;
 }
 
 div.block input[type="checkbox"]:checked::before {
-	left: 24px;
+  left: 24px;
 }
 
 body.rtl div.block input[type="checkbox"]:checked::before {
-	left: auto;
-	right: 24px;
+  left: auto;
+  right: 24px;
 }
 
 div.buttons {
-	display: flex;
-	margin: 0 15px;
-	margin-bottom: 15px;
-	margin-top: 15px;
-	flex-wrap: wrap;
-	align-items: center;
-	justify-content: start;
+  display: flex;
+  margin: 0 15px;
+  margin-bottom: 15px;
+  margin-top: 15px;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: start;
 }
 
 .button {
-	color: var(--text);
-	font-size: 16px;
-	font-weight: bold;
-	text-decoration: none;
-	cursor: pointer;
-	transition-duration: 0.1s;
+  color: var(--text);
+  font-size: 16px;
+  font-weight: bold;
+  text-decoration: none;
+  cursor: pointer;
+  transition-duration: 0.1s;
 }
 
 .button:hover {
-	color: var(--active);
+  color: var(--active);
 }
 
 .button svg {
-	width: auto;
-	height: auto;
-	padding: 0;
-	margin-right: 5px;
+  width: auto;
+  height: auto;
+  padding: 0;
+  margin-right: 5px;
 }
 
 .button:hover svg {
-	color: var(--active);
+  color: var(--active);
 }
 
 .button-inline {
-	display: inline-flex;
-	align-items: center;
-	margin: 7.5px 0;
-	background-color: var(--bg-secondary);
-	border-radius: 5px;
-	padding: 10px;
+  display: inline-flex;
+  align-items: center;
+  margin: 7.5px 0;
+  background-color: var(--bg-secondary);
+  border-radius: 5px;
+  padding: 10px;
 }
 
 .button:active {
-	transform: translateY(1px);
+  transform: translateY(1px);
 }
 
 button svg {
-	color: var(--text);
+  color: var(--text);
 }
 
 div.checklist div {
-	justify-content: space-between;
-	margin: 5px 15px;
-	padding: 10px 0;
-	word-wrap: break-word;
-	display: flex;
+  justify-content: space-between;
+  margin: 5px 15px;
+  padding: 10px 0;
+  word-wrap: break-word;
+  display: flex;
 }
 
 div.checklist a {
-	text-decoration: none;
-	color: var(--text);
+  text-decoration: none;
+  color: var(--text);
 }
 
 div.checklist a:hover {
-	text-decoration: underline;
+  text-decoration: underline;
 }
 
 div.custom-checklist x a {
-	color: var(--active);
+  color: var(--active);
 }
 
 button.add {
-	background-color: transparent;
-	border: none;
-	padding: 0;
-	margin: 0;
-	text-decoration: none;
-	display: inline-block;
-	cursor: pointer;
+  background-color: transparent;
+  border: none;
+  padding: 0;
+  margin: 0;
+  text-decoration: none;
+  display: inline-block;
+  cursor: pointer;
 }
 
 body.light-theme {
-	--text: black;
-	--bg-main: white;
-	--bg-secondary: #e4e4e4;
-	--active: #fb9817;
+  --text: black;
+  --bg-main: white;
+  --bg-secondary: #e4e4e4;
+  --active: #fb9817;
 }
 
 body.light-theme select {
-	border: 1px solid black;
+  border: 1px solid black;
 }
 
 body.light-theme a {
-	color: black;
+  color: black;
 }
 
 body.light-theme a:hover {
-	color: var(--active)
+  color: var(--active);
 }
 
 button {
-	background-color: transparent;
-	color: var(--text);
-	border: none;
-	text-decoration: none;
-	display: inline-block;
-	cursor: pointer;
-	border-radius: 5px;
+  background-color: transparent;
+  color: var(--text);
+  border: none;
+  text-decoration: none;
+  display: inline-block;
+  cursor: pointer;
+  border-radius: 5px;
 }
 
 body div section {
-	display: none;
+  display: none;
 }
 
 select:disabled {
-	opacity: 0.6;
-	cursor: not-allowed;
+  opacity: 0.6;
+  cursor: not-allowed;
 }
 
 input:disabled {
-	opacity: 0.6;
-	cursor: not-allowed;
+  opacity: 0.6;
+  cursor: not-allowed;
 }
 
 @media (max-width: 1250px) {
-	body.option {
-		flex-direction: column;
-		width: 95vw;
-		align-items: center;
-		padding: 40px 0px;
-	}
+  body.option {
+    flex-direction: column;
+    width: 95vw;
+    align-items: center;
+    padding: 40px 0px;
+  }
 
-	section.links {
-		flex-direction: row;
-		width: 95vw;
-		padding: 0 55px;
-	}
+  section.links {
+    flex-direction: row;
+    width: 95vw;
+    padding: 0 55px;
+  }
 
-	section.block-option {
-		width: 95vw;
-	}
+  section.block-option {
+    width: 95vw;
+  }
 
-	div.checklist div x {
-		overflow: hidden;
-	}
+  div.checklist div x {
+    overflow: hidden;
+  }
 }
 
 html.mobile img,
 html.mobile svg {
-	margin-right: 10px;
-	height: 30px;
-	width: 30px;
-	color: var(--text);
+  margin-right: 10px;
+  height: 30px;
+  width: 30px;
+  color: var(--text);
 }
 
 html.mobile div.block {
-	padding: 0 15px;
-	justify-content: space-between;
-	display: flex;
-	align-items: center;
-	margin-top: 20px;
-	margin-bottom: 20px;
+  padding: 0 15px;
+  justify-content: space-between;
+  display: flex;
+  align-items: center;
+  margin-top: 20px;
+  margin-bottom: 20px;
 }
 
 html.mobile div.block input[type="checkbox"] {
-	width: 58px;
-	height: 30px;
+  width: 58px;
+  height: 30px;
 }
 
 html.mobile div.block input[type="checkbox"]::before {
-	width: 24px;
-	height: 24px;
-	top: 3px;
-	left: 3.5px;
+  width: 24px;
+  height: 24px;
+  top: 3px;
+  left: 3.5px;
 }
 
 html.mobile div.block input[type="checkbox"]:checked::before {
-	left: 30px;
+  left: 30px;
 }
 
 html.mobile body.option {
-	flex-direction: column;
-	width: 100%;
-	padding: 0;
-	align-items: center;
+  flex-direction: column;
+  width: 100%;
+  padding: 0;
+  align-items: center;
 }
 
-
 html.mobile section.links {
-	flex-direction: row;
-	width: 100%;
-	padding: 0 55px;
+  flex-direction: row;
+  width: 100%;
+  padding: 0 55px;
 }
 
 html.mobile section.block-option {
-	width: 100%;
-}
\ No newline at end of file
+  width: 100%;
+}
diff --git a/src/pages/widgets/head.pug b/src/pages/widgets/head.pug
deleted file mode 100644
index 0c6e9fdb..00000000
--- a/src/pages/widgets/head.pug
+++ /dev/null
@@ -1,7 +0,0 @@
-head
-    meta(charset="utf-8")
-    meta(name="viewport" content="width=device-width, initial-scale=1")
-    link(rel="icon" type="image/x-icon" href="../../../assets/images/libredirect.svg")
-    link(href="../stylesheets/styles.css" rel="stylesheet")
-    title Settings
-    script(type="module" src="./init.js")
\ No newline at end of file
diff --git a/src/pages/widgets/links.pug b/src/pages/widgets/links.pug
deleted file mode 100644
index 318c72f9..00000000
--- a/src/pages/widgets/links.pug
+++ /dev/null
@@ -1,22 +0,0 @@
-section(class="links" id="links")
-    div(class="title")
-        a(href="#general") 
-            include /src/assets/images/general-icon.svg
-            span(data-localise="__MSG_general__") General
-
-    each val, key in services
-        div(class="title" id=`${key}-link`)
-            a(href="#"+key)
-                if services[key].imageType == 'svgMono'
-                    img(class='dark' src=`/assets/images/${key}-icon.svg`)
-                    img(class='light' src=`/assets/images/${key}-icon-light.svg`)
-                else
-                    img(src=`/assets/images/${key}-icon.${services[key].imageType}`)
-                span=services[key].name
-
-    div(class="title")
-        a(target="_blank" href="https://libredirect.github.io")
-            img(class='dark' src="/assets/images/about-icon.svg")
-            img(class='light' src="/assets/images/about-icon-light.svg")
-
-            span(data-localise="__MSG_about__") About
diff --git a/test-conditions.md b/test-conditions.md
index 843b263e..7909a1a2 100644
--- a/test-conditions.md
+++ b/test-conditions.md
@@ -16,11 +16,11 @@ YouTube Music (Tested with YouTube turned off)
 Homepage - [https://music.youtube.com](https://music.youtube.com)
 Page - [https://hyperpipe.surge.sh/channel/UCPC0L1d253x-KuMNwa05TpA](https://hyperpipe.surge.sh/channel/UCPC0L1d253x-KuMNwa05TpA)
 Explore - [https://hyperpipe.surge.sh/explore/](https://hyperpipe.surge.sh/explore/)
-  
+
 YT Embeds - [https://famiboards.com/threads/nintendo-switch-sports-announced-launches-april-29th-update-main-theme-in-threadmarks.1907/](https://famiboards.com/threads/nintendo-switch-sports-announced-launches-april-29th-update-main-theme-in-threadmarks.1907/)
 
 Twitch -[https://www.twitch.tv/pokimane](https://www.twitch.tv/pokimane)
-  
+
 TikTok - [https://www.tiktok.com/@zoecolletti?lang=en](https://www.tiktok.com/@zoecolletti?lang=en)
 
 Reddit & Imgur `(Embeds)` - [https://www.reddit.com/61ns2w/](https://www.reddit.com/61ns2w/)
@@ -32,22 +32,22 @@ Quora - [https://www.quora.com/What-is-the-equivalent-weight-of-hydrocloric-acid
 Pinterest - [https://www.pinterest.com/aldiukstores/aldi-recipes/](https://www.pinterest.com/aldiukstores/aldi-recipes/)
 
 IMDb - [https://www.imdb.com/title/tt23556786/](https://www.imdb.com/title/tt23556786/) **[Check if new URL schemes are supported by the Dev.]**
-  
+
 Fandom - [https://naruto.fandom.com](https://naruto.fandom.com)
-  
+
 Genius - [https://genius.com/Doja-cat-demons-lyrics](https://genius.com/Doja-cat-demons-lyrics)
-  
+
 Urbandictionary - [https://urbandictionary.com/define.php?term=Roads](https://urbandictionary.com/define.php?term=Roads)
-  
+
 Stackoverflow - [https://stackoverflow.com/questions/16330404/how-to-remove-remote-origin-from-a-git-repository](https://stackoverflow.com/questions/16330404/how-to-remove-remote-origin-from-a-git-repository)
-  
+
 Goodreads - [https://www.goodreads.com/book/show/3869.A_Brief_History_of_Time](https://www.goodreads.com/book/show/3869.A_Brief_History_of_Time)
-  
+
 Bandcamp - [https://thorwegian.bandcamp.com/track/just-because](https://thorwegian.bandcamp.com/track/just-because)
-  
+
 Instructables - [https://instructables.com/DIY-Arduino-Obstacle-Avoiding-Car-at-Home/](https://instructables.com/DIY-Arduino-Obstacle-Avoiding-Car-at-Home/)
-  
-Web archive - [https://web.archive.org/web/20230131222432if_/https://www.dailymail.co.uk/news/article-11687675/Army-spied-lockdown-critics-Sceptics-including-Peter-Hitchens-suspected-watched.html](https://web.archive.org/web/20230131222432if_/https://www.dailymail.co.uk/news/article-11687675/Army-spied-lockdown-critics-Sceptics-including-Peter-Hitchens-suspected-watched.html)
+
+Web archive - [https://web.archive.org/web/20230131222432if\_/https://www.dailymail.co.uk/news/article-11687675/Army-spied-lockdown-critics-Sceptics-including-Peter-Hitchens-suspected-watched.html](https://web.archive.org/web/20230131222432if_/https://www.dailymail.co.uk/news/article-11687675/Army-spied-lockdown-critics-Sceptics-including-Peter-Hitchens-suspected-watched.html)
 
 ---