diff --git a/css/Flags/Dark.css b/css/Flags/Dark.css
index da1bcda..589bfcf 100644
--- a/css/Flags/Dark.css
+++ b/css/Flags/Dark.css
@@ -1,6 +1,6 @@
body.dark {
--color: #EEE;
- --color2: #EEE9;
+ --color2: #EEE6;
--background: #1118;
--bg2: #0008;
--hover: #FFF1;
diff --git a/css/Pages/Pages.css b/css/Pages/Pages.css
index 02e7e0d..4fa4c1a 100644
--- a/css/Pages/Pages.css
+++ b/css/Pages/Pages.css
@@ -1,6 +1,6 @@
@import url(Home.css);
@import url(Services.css);
-@import url(Settings.css);
+@import url(Settings/Settings.css);
.page {
position: fixed;
@@ -80,3 +80,36 @@
.header .text {
font-size: 26px;
}
+
+.sub-switch {
+ display: flex;
+ background: var(--bg2);
+ transition: background .2s;
+ margin: 16px 24px 0;
+ z-index: 1;
+ padding: 4px;
+ border-radius: 16px;
+ position: relative;
+ overflow: hidden;
+ --id: inherit;
+}
+
+.sub-switch::before {
+ content: " ";
+ z-index: -1;
+ position: absolute;
+ top: 4px;
+ left: calc(var(--id) / var(--switches) * 100% + 4px - 4px * var(--id));
+ width: calc(100% / var(--switches) - 4px);
+ height: calc(100% - 8px);
+ opacity: .25;
+ background: var(--color2);
+ border-radius: 12px;
+ transition: left .3s, background .3s;
+}
+
+.sub-switch div {
+ padding: 12px;
+ width: 50%;
+ cursor: pointer;
+}
diff --git a/css/Pages/Settings.css b/css/Pages/Settings.css
deleted file mode 100644
index d613640..0000000
--- a/css/Pages/Settings.css
+++ /dev/null
@@ -1,72 +0,0 @@
-.settings {
- margin: 32px auto;
- padding: 0 16px;
-}
-
-.setting {
- background: var(--bg2);
- margin: 8px;
- padding: 20px;
- display: flex;
- cursor: pointer;
- border-radius: 16px;
- align-items: center;
- text-align: left;
- transition: background .3s;
-}
-
-.setting i {
- margin-right: 14px;
- font-size: 28px;
-}
-
-.setting .name {
- font-size: 16px;
-}
-
-.setting .desc {
- opacity: .6;
- margin-right: 16px;
-}
-
-.setting .switch {
- position: relative;
- width: 44px;
- min-width: 44px;
- height: 24px;
- background: #8886;
- border-radius: 100px;
- margin: 0 4px 0 auto;
- transition: border .4s, background .3s;
-}
-
-.setting .switch:after {
- content: "";
- position: absolute;
- width: 16px;
- height: 16px;
- background: var(--color);
- left: 4px;
- top: 50%;
- border-radius: 10px;
- transform: translateY(-50%);
- transition: left .2s, background .3s;
-}
-
-.setting.checked .switch {
- background-color: #68F;
- border-color: #68F;
-}
-
-.setting.checked .switch:after {
- left: calc(100% - 20px);
-}
-
-.warn {
- margin: 24px 0 -8px;
- color: #F60;
-}
-
-.warn.hidden {
- display: none;
-}
diff --git a/css/Pages/Settings/Settings.css b/css/Pages/Settings/Settings.css
new file mode 100644
index 0000000..6a23312
--- /dev/null
+++ b/css/Pages/Settings/Settings.css
@@ -0,0 +1,165 @@
+.settings {
+ margin: 32px auto;
+ padding: 0 16px;
+}
+
+.setting {
+ background: var(--bg2);
+ margin: 8px;
+ padding: 20px;
+ display: flex;
+ cursor: pointer;
+ border-radius: 16px;
+ align-items: center;
+ text-align: left;
+ transition: background .3s;
+}
+
+.setting i {
+ margin-right: 14px;
+ font-size: 28px;
+}
+
+.setting .name {
+ font-size: 16px;
+}
+
+.setting .desc {
+ opacity: .6;
+ margin-right: 16px;
+}
+
+.setting .switch {
+ position: relative;
+ width: 44px;
+ min-width: 44px;
+ height: 24px;
+ background: #8886;
+ border-radius: 100px;
+ margin: 0 4px 0 auto;
+ transition: border .4s, background .3s;
+}
+
+.setting .switch:after {
+ content: "";
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ background: var(--color);
+ left: 4px;
+ top: 50%;
+ border-radius: 10px;
+ transform: translateY(-50%);
+ transition: left .2s, background .3s;
+}
+
+.setting.checked .switch {
+ background-color: #68F;
+ border-color: #68F;
+}
+
+.setting.checked .switch:after {
+ left: calc(100% - 20px);
+}
+
+#no-cookies {
+ margin: 24px 0 -8px;
+ color: #F60;
+}
+
+#no-cookies.hidden {
+ display: none;
+}
+
+.subpages {
+ position: relative;
+ transform: translateX(calc(var(--id) * -100%));
+ transition: transform .4s, height .4s;
+}
+
+.subpages > div {
+ position: absolute;
+ left: calc(var(--n) * 100%);
+ width: 100%;
+}
+
+#report-boxes {
+ display: flex;
+ text-align: left;
+ flex-wrap: wrap;
+ margin-bottom: 8px;
+}
+
+#report-boxes i::after {
+ color: var(--color);
+ text-shadow: 0 0 48px var(--color);
+ padding: 16px;
+ font-size: 28px;
+ border-radius: 32px;
+}
+
+#report-boxes .icon-https::after {
+ --color: #0C7;
+ content: "lock";
+}
+
+#report-boxes .icon-rocket::after {
+ --color: #68F;
+ content: "rocket_launch";
+}
+
+#report-boxes .prewarn i::after {
+ --color: #EA0;
+}
+
+#report-boxes .warn i::after {
+ --color: #F60;
+}
+
+#report-boxes .error i::after {
+ --color: #F22;
+}
+
+#report-boxes > div {
+ display: flex;
+ width: 50%;
+ min-width: 256px;
+ flex: 1;
+ padding: 20px 8px;
+ align-items: center;
+}
+
+#report-boxes .title {
+ font-size: 16px;
+}
+
+#report-boxes .subtitle {
+ opacity: .6;
+}
+
+.security {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ max-width: 600px;
+ padding: 24px;
+ margin: 120px auto 80px;
+ text-align: right;
+}
+
+.security i {
+ font-size: 80px;
+}
+
+.security #report-score {
+ font-size: 48px;
+}
+
+.security #report {
+ opacity: .6;
+}
+
+.score::after {
+ content: "%";
+ font-size: 18px;
+}
diff --git a/index.html b/index.html
index 41cefe7..179a3cf 100644
--- a/index.html
+++ b/index.html
@@ -48,16 +48,34 @@
-
+
diff --git a/js/UI/Settings/Settings.js b/js/UI/Settings/Settings.js
index a24e9a6..1c4ecab 100644
--- a/js/UI/Settings/Settings.js
+++ b/js/UI/Settings/Settings.js
@@ -1,5 +1,8 @@
import App from "../../App";
import * as EVENTS from "./events";
+import { analyzeService } from "./security";
+import * as tiles from "./tiles"
+
export default class Settings {
constructor() {
@@ -10,69 +13,148 @@ export default class Settings {
init() {
this.checkLocalStorage()
+ this.initSecurityPanel()
+ this.initSettings()
+ this.initPager()
+ }
- let darkMode = this.addOnOffTile(
+ initSettings() {
+ let darkMode = tiles.addOnOffTile(this.config,
"dark_mode", "Dark mode",
"Make the colors more appropriate for low-light environments",
"dark_mode", EVENTS.onThemeChange
)
- document.getElementById("theme-switcher").addEventListener("click", () => {
- darkMode.click()
- })
- this.addOnOffTile(
+ tiles.addOnOffTile(this.config,
"open_in_new", "Open in new tab",
"Clicking on application will open it in a new browser tab",
"open_new_tab", EVENTS.onNewTabChange
)
- this.addOnOffTile(
+ tiles.addOnOffTile(this.config,
"blur_on", "Enable blur",
"Improves UI sweetness but may have a huge impact on performance",
"blur", EVENTS.onBlurChange
)
- this.addOnOffTile(
+ tiles.addOnOffTile(this.config,
"animation", "Animations",
"Show nice and fancy page transitions for improved experience",
"animations", EVENTS.onAnimationChange
)
+
+ document.getElementById("theme-switcher").addEventListener("click", () => {
+ darkMode.click()
+ })
}
- addOnOffTile(icon, name, desc, key, func) {
- let item = document.createElement("div")
- item.classList.add("setting")
- item.innerHTML = `
- ${icon}
-
- `
+ initPager() {
+ let switcher = document.getElementById("switch")
+ let buttons = switcher.children
+ let subsettings = document.getElementById("subsettings")
- let handleState = () => {
- let c = item.classList
- if (this.config.get(key)) c.add("checked")
- else c.remove("checked")
+ for (let i = 0; i < buttons.length; i++) {
+ let button = buttons[i]
+ subsettings.children[i].setAttribute("style", `--n: ${i}`)
+
+ button.addEventListener("click", () => {
+ let calculatedHeight = subsettings.children[i].offsetHeight
+ subsettings.style.height = `${calculatedHeight}px`
+ subsettings.parentNode.setAttribute("style", `--id: ${i}`)
+ switcher.setAttribute("style", `--switches: ${buttons.length}`)
+ })
}
- let write = () => {
- let target_value = !this.config.get(key)
- this.config.set(key, target_value)
+
+ buttons[0].click()
+ }
+
+ initSecurityPanel() {
+ let stats = {
+ total: 0,
+ secure: 0,
+ thirdParties: 0
+ }
+ for (let service of this.config.getServices()) {
+ let analysis = analyzeService(service.href)
+ stats.total++
+ stats.secure += analysis.isSecure
+ stats.thirdParties += analysis.isThirdParty
}
- let f = () => {func(this.config)}
- item.addEventListener("click", write)
- item.addEventListener("click", handleState)
- if (func) item.addEventListener("click", f)
+ let secure_pp = 100 * stats.secure / stats.total
+ let indepencence_pp = 100 * (stats.total - stats.thirdParties) / stats.total
+ let privacy_score = secure_pp * 0.5 + indepencence_pp * 0.5
- handleState()
- if (func && this.config.changed(key)) f()
- document.getElementById("settings").appendChild(item)
- return item
+ let privacy_report;
+ if (privacy_score == 100) {
+ privacy_report = "Data is sent securely and no third-party services are involved."
+ }
+ else if (privacy_score >= 85) {
+ privacy_report = "Great privacy overall, but at some point it could be better."
+ }
+ else if (privacy_score >= 40) {
+ privacy_report = "Decent privacy. You should pay attention what you're doing."
+ }
+ else privacy_report = "Transferring data is insecure and vulnerable to various threats."
+
+ document.getElementById("report-score").innerText = Math.round(privacy_score)
+ document.getElementById("report").innerText = privacy_report
+
+ const phrase = " of the listed services are using secure connections"
+ tiles.privacyBox(secure_pp, "icon-https", [
+ {
+ "from": 0,
+ "name": "No encryption",
+ "desc": "None" + phrase
+ },
+ {
+ "from": 0.1,
+ "name": "Poor encryption",
+ "desc": "Only some" + phrase
+ },
+ {
+ "from": 60,
+ "name": "Fair encryption",
+ "desc": "Most" + phrase
+ },
+ {
+ "from": 90,
+ "name": "Good encryption",
+ "desc": "Nearly all" + phrase
+ },
+ {
+ "from": 100,
+ "name": "Full encryption",
+ "desc": "All" + phrase
+ }
+ ])
+
+ tiles.privacyBox(indepencence_pp, "icon-rocket", [
+ {
+ "from": 0,
+ "name": "Not independent",
+ "desc": "This server lists a lot of 3rd party services"
+ },
+ {
+ "from": 40,
+ "name": "Partially independent",
+ "desc": "This server lists many 3rd party services"
+ },
+ {
+ "from": 65,
+ "name": "Mostly independent",
+ "desc": "This server still lists some 3rd party services"
+ },
+ {
+ "from": 100,
+ "name": "Fully independent",
+ "desc": "This server is free of 3rd party services"
+ }
+ ])
}
checkLocalStorage() {
let warn = document.getElementById("no-cookies").classList
if (this.config.storageAvailable) warn.add("hidden")
}
-}
\ No newline at end of file
+}
diff --git a/js/UI/Settings/security.js b/js/UI/Settings/security.js
new file mode 100644
index 0000000..eefcec5
--- /dev/null
+++ b/js/UI/Settings/security.js
@@ -0,0 +1,32 @@
+export function analyzeService(url) {
+ let isSiteSecure = (
+ window.location.protocol == "https:" ||
+ window.location.hostname == "localhost"
+ )
+
+ let domain_base = window.location.hostname
+ let isSecure = false
+ let isThirdParty = true
+
+ if (url.startsWith("https://")) {
+ isSecure = true
+ }
+ else if (!["http", "https"].includes(url.split("://")[0])) {
+ isSecure = isSiteSecure
+ }
+
+ if (isThirdParty) {
+ let domain = url.split("://")
+ if (domain.length > 1) {
+ domain = domain[1]
+ domain = domain.split("/")[0]
+ domain = domain.split(":")[0]
+ isThirdParty = !domain.includes(domain_base)
+ }
+ else {
+ isThirdParty = false
+ }
+ }
+
+ return {isSecure, isThirdParty}
+}
\ No newline at end of file
diff --git a/js/UI/Settings/tiles.js b/js/UI/Settings/tiles.js
new file mode 100644
index 0000000..2a535af
--- /dev/null
+++ b/js/UI/Settings/tiles.js
@@ -0,0 +1,59 @@
+export function addOnOffTile(conf, icon, name, desc, key, func) {
+ let item = document.createElement("div")
+ item.classList.add("setting")
+ item.innerHTML = `
+ ${icon}
+
+ `
+
+ let handleState = () => {
+ let c = item.classList
+ if (conf.get(key)) c.add("checked")
+ else c.remove("checked")
+ }
+ let write = () => {
+ let target_value = !conf.get(key)
+ conf.set(key, target_value)
+ }
+ let f = () => {func(conf)}
+
+ item.addEventListener("click", write)
+ item.addEventListener("click", handleState)
+ if (func) item.addEventListener("click", f)
+
+ handleState()
+ if (func && conf.changed(key)) f()
+ document.getElementById("settings").appendChild(item)
+ return item
+}
+
+export function privacyBox(privacyScore, icon_class, levels) {
+ let name, desc
+ levels = levels.reverse()
+
+ for (let i in levels) {
+ let item = levels[i]
+ if (item.from <= privacyScore) {
+ name = levels[i].name
+ desc = levels[i].desc
+ break
+ }
+ }
+
+ let item = document.createElement("div")
+ item.innerHTML = `
+ `
+
+ if (privacyScore < 30) item.classList.add("error")
+ else if (privacyScore < 90) item.classList.add("warn")
+ else if (privacyScore < 100) item.classList.add("prewarn")
+
+ document.getElementById("report-boxes").appendChild(item)
+ return item
+}