about summary refs log tree commit diff stats
path: root/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/options/index.js399
-rw-r--r--src/pages/options/index.pug10
-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/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/widgets/head.pug7
-rw-r--r--src/pages/widgets/links.pug22
34 files changed, 1196 insertions, 825 deletions
diff --git a/src/pages/options/index.js b/src/pages/options/index.js
deleted file mode 100644
index 8d5676e0..00000000
--- a/src/pages/options/index.js
+++ /dev/null
@@ -1,399 +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) {
-        var { color, text } = processTime(time)
-      }
-      const timeText = time ? `<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
-}
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/widgets/general.js b/src/pages/options/widgets/general.js
deleted file mode 100644
index b9ddfd08..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/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/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