Merge pull request #581 from PR0M3TH3AN/codex/add-theme-accent-overrides-configuration

Add configurable accent overrides for light and dark themes
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-15 19:25:56 -04:00
commit 312465c6e9
4 files changed
+111

No files matched your search

+4
View File
@@ -182,6 +182,10 @@ artifacts.
the fee at `0` to pass through every satoshi.
- Populate `DEFAULT_RELAY_URLS_OVERRIDE` with WSS URLs to replace the bundled
relay bootstrap list. Keep it empty to stick with the upstream defaults.
- Customize `THEME_ACCENT_OVERRIDES` with `#RRGGBB` hex strings when you want
light or dark mode to use different accent, accent-strong, or
accent-pressed colors. Leave the values `null` to inherit the defaults from
`css/tokens.css`.
- **`js/config.js`**:
- Re-exports `isDevMode` (derived from `IS_DEV_MODE`) for modules, publishes
`window.__BITVID_DEV_MODE__`, and centralizes the global configuration
+22
View File
@@ -125,6 +125,28 @@ export const DEFAULT_WHITELIST_MODE_ENABLED = true;
*/
export const ALLOW_NSFW_CONTENT = false;
/**
* Optional theme accent overrides for light and dark mode.
*
* Operators can provide hex color strings (e.g., `"#2563eb"`) to customize the
* accent tokens without editing the generated CSS bundle. Leave values as
* `null` to keep the upstream defaults defined in `css/tokens.css`. Each theme
* accepts overrides for the base accent color as well as the stronger and
* pressed states used for hover/active presentations.
*/
export const THEME_ACCENT_OVERRIDES = Object.freeze({
light: Object.freeze({
accent: null,
accentStrong: null,
accentPressed: null,
}),
dark: Object.freeze({
accent: null,
accentStrong: null,
accentPressed: null,
}),
});
/**
* Nostr kind used when persisting watch history events.
*
+49
View File
@@ -7,6 +7,7 @@ import {
ADMIN_SUPER_NPUB,
PLATFORM_FEE_PERCENT,
PLATFORM_LUD16_OVERRIDE,
THEME_ACCENT_OVERRIDES,
} from "./instance-config.js";
function assertSuperAdminConfigured(value) {
@@ -56,6 +57,50 @@ function getPlatformLud16Override(value) {
return value.trim();
}
const ACCENT_HEX_PATTERN = /^#(?:[0-9a-fA-F]{6})$/;
function assertThemeAccentOverrides(overrides) {
const source =
overrides && typeof overrides === "object"
? overrides
: THEME_ACCENT_OVERRIDES;
const allowedThemes = new Set(["light", "dark"]);
const allowedTokens = ["accent", "accentStrong", "accentPressed"];
Object.entries(source).forEach(([theme, themeOverrides]) => {
if (!allowedThemes.has(theme) || themeOverrides == null) {
return;
}
if (typeof themeOverrides !== "object") {
throw new Error(
`THEME_ACCENT_OVERRIDES.${theme} must be an object with accent overrides.`
);
}
allowedTokens.forEach((token) => {
const rawValue = themeOverrides[token];
if (rawValue == null || rawValue === "") {
return;
}
if (typeof rawValue !== "string") {
throw new Error(
`THEME_ACCENT_OVERRIDES.${theme}.${token} must be a hex string (e.g., '#2563eb').`
);
}
const trimmed = rawValue.trim();
if (!ACCENT_HEX_PATTERN.test(trimmed)) {
throw new Error(
`THEME_ACCENT_OVERRIDES.${theme}.${token} must be a #RRGGBB hex color.`
);
}
});
});
}
function resolveConfig(overrides = {}) {
return {
adminSuperNpub:
@@ -64,6 +109,8 @@ function resolveConfig(overrides = {}) {
overrides.PLATFORM_FEE_PERCENT ?? PLATFORM_FEE_PERCENT,
platformLud16Override:
overrides.PLATFORM_LUD16_OVERRIDE ?? PLATFORM_LUD16_OVERRIDE,
themeAccentOverrides:
overrides.THEME_ACCENT_OVERRIDES ?? THEME_ACCENT_OVERRIDES,
};
}
@@ -82,4 +129,6 @@ export function validateInstanceConfig(overrides) {
"PLATFORM_FEE_PERCENT is positive but PLATFORM_LUD16_OVERRIDE is empty. Set PLATFORM_LUD16_OVERRIDE to the Lightning address that should receive the platform's split, or publish a lud16 value on the Super Admin profile so bitvid can route platform fees."
);
}
assertThemeAccentOverrides(config.themeAccentOverrides);
}
+36
View File
@@ -1,3 +1,4 @@
import { THEME_ACCENT_OVERRIDES } from "../config/instance-config.js";
import { userLogger } from "./utils/logger.js";
const STORAGE_KEY = "bitvid:theme";
const FALLBACK_THEME = "dark";
@@ -13,6 +14,12 @@ const TOGGLE_SELECTORS = [
"#themeToggle",
];
const ACCENT_CSS_VARIABLES = Object.freeze({
accent: "--color-accent",
accentStrong: "--color-accent-strong",
accentPressed: "--color-accent-pressed",
});
const boundToggles = new WeakSet();
const registeredToggles = new Set();
let currentTheme = null;
@@ -70,6 +77,34 @@ const getRootElement = () => {
return null;
};
const normalizeAccentValue = (value) =>
typeof value === "string" ? value.trim() : "";
const applyAccentOverrides = (root, theme) => {
if (!root) {
return;
}
const themeOverrides =
THEME_ACCENT_OVERRIDES &&
typeof THEME_ACCENT_OVERRIDES === "object" &&
THEME_ACCENT_OVERRIDES[theme] &&
typeof THEME_ACCENT_OVERRIDES[theme] === "object"
? THEME_ACCENT_OVERRIDES[theme]
: null;
Object.entries(ACCENT_CSS_VARIABLES).forEach(([token, cssVariable]) => {
const overrideValue = themeOverrides ? themeOverrides[token] : null;
const normalized = normalizeAccentValue(overrideValue);
if (normalized) {
root.style.setProperty(cssVariable, normalized);
} else {
root.style.removeProperty(cssVariable);
}
});
};
const updateThemeColorMeta = (theme) => {
if (!isBrowser()) {
return;
@@ -202,6 +237,7 @@ const applyTheme = (theme, { persist = true } = {}) => {
const root = getRootElement();
if (root) {
root.dataset.theme = normalized;
applyAccentOverrides(root, normalized);
}
if (persist) {