diff --git a/src/popup/index.html b/src/popup/index.html
index 2c62210..34d6997 100644
--- a/src/popup/index.html
+++ b/src/popup/index.html
@@ -824,7 +824,7 @@
diff --git a/src/popup/index.js b/src/popup/index.js
index f343d6f..5c98a60 100644
--- a/src/popup/index.js
+++ b/src/popup/index.js
@@ -6,6 +6,7 @@ const { state, saveState, loadState } = require("../shared/state");
const { refreshPrices } = require("../shared/prices");
const { refreshBalances } = require("../shared/balances");
const { $, showView } = require("./views/helpers");
+const { applyTheme } = require("./theme");
const home = require("./views/home");
const welcome = require("./views/welcome");
@@ -176,6 +177,7 @@ async function init() {
}
await loadState();
+ applyTheme(state.theme);
// Auto-default active address
if (
diff --git a/src/popup/styles/main.css b/src/popup/styles/main.css
index f7f86a5..37cc00b 100644
--- a/src/popup/styles/main.css
+++ b/src/popup/styles/main.css
@@ -15,6 +15,18 @@
--color-section: #dddddd;
}
+html.dark {
+ --color-bg: #000000;
+ --color-fg: #ffffff;
+ --color-muted: #999999;
+ --color-border: #ffffff;
+ --color-border-light: #333333;
+ --color-hover: #111111;
+ --color-well: #0a0a0a;
+ --color-danger-well: #1a0000;
+ --color-section: #222222;
+}
+
body {
width: 396px;
overflow-x: hidden;
diff --git a/src/popup/theme.js b/src/popup/theme.js
new file mode 100644
index 0000000..0032a3c
--- /dev/null
+++ b/src/popup/theme.js
@@ -0,0 +1,33 @@
+// Theme management: applies light/dark class to based on preference.
+
+let mediaQuery = null;
+let mediaHandler = null;
+
+function applyTheme(theme) {
+ // Clean up previous system listener
+ if (mediaQuery && mediaHandler) {
+ mediaQuery.removeEventListener("change", mediaHandler);
+ mediaHandler = null;
+ }
+
+ if (theme === "dark") {
+ document.documentElement.classList.add("dark");
+ } else if (theme === "light") {
+ document.documentElement.classList.remove("dark");
+ } else {
+ // system
+ mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+ const update = () => {
+ if (mediaQuery.matches) {
+ document.documentElement.classList.add("dark");
+ } else {
+ document.documentElement.classList.remove("dark");
+ }
+ };
+ mediaHandler = update;
+ mediaQuery.addEventListener("change", update);
+ update();
+ }
+}
+
+module.exports = { applyTheme };
diff --git a/src/popup/views/settings.js b/src/popup/views/settings.js
index ea67337..b1b91b8 100644
--- a/src/popup/views/settings.js
+++ b/src/popup/views/settings.js
@@ -1,4 +1,5 @@
const { $, showView, showFlash, escapeHtml } = require("./helpers");
+const { applyTheme } = require("../theme");
const { state, saveState } = require("../../shared/state");
const { ETHEREUM_MAINNET_CHAIN_ID } = require("../../shared/constants");
const { log, debugFetch } = require("../../shared/log");
@@ -214,6 +215,13 @@ function init(ctx) {
await saveState();
});
+ $("settings-theme").value = state.theme;
+ $("settings-theme").addEventListener("change", async () => {
+ state.theme = $("settings-theme").value;
+ await saveState();
+ applyTheme(state.theme);
+ });
+
$("settings-hide-low-holders").checked = state.hideLowHolderTokens;
$("settings-hide-low-holders").addEventListener("change", async () => {
state.hideLowHolderTokens = $("settings-hide-low-holders").checked;
diff --git a/src/shared/state.js b/src/shared/state.js
index a98d84c..0331439 100644
--- a/src/shared/state.js
+++ b/src/shared/state.js
@@ -25,6 +25,7 @@ const DEFAULT_STATE = {
dustThresholdGwei: 100000,
fraudContracts: [],
tokenHolderCache: {},
+ theme: "system",
};
const state = {
@@ -55,6 +56,7 @@ async function saveState() {
dustThresholdGwei: state.dustThresholdGwei,
fraudContracts: state.fraudContracts,
tokenHolderCache: state.tokenHolderCache,
+ theme: state.theme,
currentView: state.currentView,
selectedWallet: state.selectedWallet,
selectedAddress: state.selectedAddress,
@@ -110,6 +112,7 @@ async function loadState() {
: 100000;
state.fraudContracts = saved.fraudContracts || [];
state.tokenHolderCache = saved.tokenHolderCache || {};
+ state.theme = saved.theme || "system";
state.currentView = saved.currentView || null;
state.selectedWallet =
saved.selectedWallet !== undefined ? saved.selectedWallet : null;