From 23a51cddd5281dde0d8a453ac3bc2a5e404c8013 Mon Sep 17 00:00:00 2001 From: LilyLavenderPony Date: Thu, 26 Mar 2026 09:12:17 +0100 Subject: [PATCH 1/8] Implement custom theming support --- src/app/plugins/colorUtils.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/app/plugins/colorUtils.ts diff --git a/src/app/plugins/colorUtils.ts b/src/app/plugins/colorUtils.ts new file mode 100644 index 0000000000..5f2e200912 --- /dev/null +++ b/src/app/plugins/colorUtils.ts @@ -0,0 +1,29 @@ +import chroma from 'chroma-js'; +import { ThemeKind } from '../hooks/useTheme'; + +export const accessibleColor = (themeKind: ThemeKind, color: string): string => { + if (!chroma.valid(color)) return color; + + let lightness = chroma(color).lab()[0]; + if (themeKind === ThemeKind.Dark && lightness < 60) { + lightness = 60; + } + if (themeKind === ThemeKind.Light && lightness > 50) { + lightness = 50; + } + + return chroma(color).set('lab.l', lightness).hex(); +}; + +export const hexToGrayscale = (hex: string): string => { + const clean = hex.replace('#', ''); + + const r = parseInt(clean.substring(0, 2), 16); + const g = parseInt(clean.substring(2, 4), 16); + const b = parseInt(clean.substring(4, 6), 16); + + const gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b); + const grayHex = gray.toString(16).padStart(2, '0'); + + return `#${grayHex}${grayHex}${grayHex}`; +} \ No newline at end of file From 3b46bfbcd12375d665e8e6b82582ccd0201ff180 Mon Sep 17 00:00:00 2001 From: LilyLavenderPony Date: Thu, 26 Mar 2026 09:53:19 +0100 Subject: [PATCH 2/8] Added custom theming --- package-lock.json | 27 +-- src/app/components/nav/styles.css.ts | 1 + src/app/components/page/Page.tsx | 3 +- src/app/components/sidebar/Sidebar.css.ts | 13 ++ src/app/features/settings/general/General.tsx | 167 +++++++++++++++--- src/app/hooks/useMemberPowerTag.ts | 2 +- src/app/pages/ThemeManager.tsx | 54 +++++- src/app/plugins/color.ts | 16 -- src/app/state/settings.ts | 19 ++ 9 files changed, 249 insertions(+), 53 deletions(-) delete mode 100644 src/app/plugins/color.ts diff --git a/package-lock.json b/package-lock.json index b7281a0d8a..4f9f845a9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6041,7 +6041,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "devOptional": true + "optional": true }, "node_modules/acorn": { "version": "8.14.0", @@ -6885,7 +6885,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "devOptional": true, + "optional": true, "engines": { "node": ">=10" } @@ -9498,7 +9498,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "devOptional": true, + "optional": true, "dependencies": { "minipass": "^3.0.0" }, @@ -9510,7 +9510,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, + "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -9522,7 +9522,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true + "optional": true }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -10448,6 +10448,7 @@ "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", "dev": true, "license": "ISC", + "optional": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -12284,7 +12285,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "devOptional": true, + "optional": true, "engines": { "node": ">=8" } @@ -12293,7 +12294,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "devOptional": true, + "optional": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -12306,7 +12307,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, + "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -12318,13 +12319,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true + "optional": true }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "devOptional": true, + "optional": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -12465,7 +12466,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "devOptional": true, + "optional": true, "dependencies": { "abbrev": "1" }, @@ -17414,7 +17415,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "devOptional": true, + "optional": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -17431,7 +17432,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true + "optional": true }, "node_modules/temp-dir": { "version": "2.0.0", diff --git a/src/app/components/nav/styles.css.ts b/src/app/components/nav/styles.css.ts index 48b2a4d344..e1e1632190 100644 --- a/src/app/components/nav/styles.css.ts +++ b/src/app/components/nav/styles.css.ts @@ -81,6 +81,7 @@ const NavItemBase = style({ }, }, }); + export const NavItem = recipe({ base: [DefaultReset, NavItemBase, Disabled], variants: { diff --git a/src/app/components/page/Page.tsx b/src/app/components/page/Page.tsx index a54563855c..e6140b796c 100644 --- a/src/app/components/page/Page.tsx +++ b/src/app/components/page/Page.tsx @@ -14,7 +14,8 @@ export function PageRoot({ nav, children }: PageRootProps) { const screenSize = useScreenSizeContext(); return ( - + //{classNames(ContainerColor({ variant: 'Background' }), backgroundFrontLayer)} + {nav} {screenSize !== ScreenSize.Mobile && ( diff --git a/src/app/components/sidebar/Sidebar.css.ts b/src/app/components/sidebar/Sidebar.css.ts index c36862232b..c67bbdbd98 100644 --- a/src/app/components/sidebar/Sidebar.css.ts +++ b/src/app/components/sidebar/Sidebar.css.ts @@ -16,6 +16,19 @@ export const Sidebar = style([ }, ]); +export const SidebarFrontLayer = style([ + DefaultReset, + { + width: toRem(66), + backgroundColor: color.Background.Container, + borderRight: `${config.borderWidth.B300} solid ${color.Background.ContainerLine}`, + + display: 'flex', + flexDirection: 'column', + color: color.Background.OnContainer, + }, +]); + export const SidebarStack = style([ DefaultReset, { diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index b861a060b7..29c1ba38ce 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -29,8 +29,11 @@ import { } from 'folds'; import { isKeyHotkey } from 'is-hotkey'; import FocusTrap from 'focus-trap-react'; +import { HexColorPicker } from 'react-colorful'; import { Page, PageContent, PageHeader } from '../../../components/page'; import { SequenceCard } from '../../../components/sequence-card'; +import { HexColorPickerPopOut } from '../../../components/HexColorPickerPopOut'; +import { PowerColorBadge } from '../../../components/power/PowerColorBadge'; import { useSetting } from '../../../state/hooks/settings'; import { DateFormat, MessageLayout, MessageSpacing, settingsAtom } from '../../../state/settings'; import { SettingTile } from '../../../components/setting-tile'; @@ -50,6 +53,8 @@ import { useMessageLayoutItems } from '../../../hooks/useMessageLayout'; import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing'; import { useDateFormatItems } from '../../../hooks/useDateFormat'; import { SequenceCardStyle } from '../styles.css'; +import { transcode } from 'buffer'; +import { current } from 'immer'; type ThemeSelectorProps = { themeNames: Record; @@ -259,54 +264,115 @@ function SystemThemePreferences() { ); } -function PageZoomInput() { - const [pageZoom, setPageZoom] = useSetting(settingsAtom, 'pageZoom'); - const [currentZoom, setCurrentZoom] = useState(`${pageZoom}`); +function NumberSettingInput({ settingKey, min = 0, max = 360 , percent = false}: NumberSettingInputProps) { + + const [value, setValue] = useSetting(settingsAtom, settingKey); + const [current, setCurrent] = useState(`${value}`); - const handleZoomChange: ChangeEventHandler = (evt) => { - setCurrentZoom(evt.target.value); + const handleChange: ChangeEventHandler = (evt) => { + setCurrent(evt.target.value); }; - const handleZoomEnter: KeyboardEventHandler = (evt) => { + const handleKeyDown: KeyboardEventHandler = (evt) => { if (isKeyHotkey('escape', evt)) { evt.stopPropagation(); - setCurrentZoom(pageZoom.toString()); + setCurrent(`${value}`); } if ( isKeyHotkey('enter', evt) && 'value' in evt.target && typeof evt.target.value === 'string' ) { - const newZoom = parseInt(evt.target.value, 10); - if (Number.isNaN(newZoom)) return; - const safeZoom = Math.max(Math.min(newZoom, 150), 75); - setPageZoom(safeZoom); - setCurrentZoom(safeZoom.toString()); + const newValue = parseInt(evt.target.value, 10); + if (Number.isNaN(newValue)) return; + + const safeValue = Math.max(Math.min(newValue, max), min); + setValue(safeValue); + setCurrent(safeValue.toString()); } }; return ( %} + min={min} + max={max} + value={current} + onChange={handleChange} + onKeyDown={handleKeyDown} + after={{percent ? '%' : ''}} outlined /> ); } +type NumberSettingKey = 'transparency' | 'pageZoom' | 'angle' | 'blur'; +interface NumberSettingInputProps { + settingKey: NumberSettingKey; + min?: number; + max?: number; + percent?: boolean; +} + +function TransparencyInput() { + return ; +} +function AngleInput() { + return ; +} +function BlurInput() { + return ; +} +function PageZoomInput() { + return ; +} + +function ColorPickerButton({ + color, + onChange, + onRemove, +}: { + color?: string; + onChange: (color: string) => void; + onRemove?: () => void; +}) { + return ( + } + onRemove={onRemove} + > + {(openPicker, opened) => ( + + )} + + ); +} + function Appearance() { const [systemTheme, setSystemTheme] = useSetting(settingsAtom, 'useSystemTheme'); const [monochromeMode, setMonochromeMode] = useSetting(settingsAtom, 'monochromeMode'); const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); + const [customBackgroundEnabled, setCustomBackgroundEnabled] = useSetting(settingsAtom,'customBackgroundEnabled'); + const [customBgColor1, setCustomBgColor1] = useSetting(settingsAtom, 'customBgColor1'); + const [customBgColor2, setCustomBgColor2] = useSetting(settingsAtom, 'customBgColor2'); + const [customBgColor3, setCustomBgColor3] = useSetting(settingsAtom, 'customBgColor3'); + const [customBgColor4, setCustomBgColor4] = useSetting(settingsAtom, 'customBgColor4'); + const [customBgColor5, setCustomBgColor5] = useSetting(settingsAtom, 'customBgColor5'); return ( @@ -333,6 +399,67 @@ function Appearance() { /> + + + } + /> + + + {customBackgroundEnabled && ( + <> + + + setCustomBgColor1('')} + /> + setCustomBgColor2('')} + /> + setCustomBgColor3('')} + /> + setCustomBgColor4('')} + /> + setCustomBgColor5('')} + /> + + + + + }/> + + + + }/> + + + {/* The blur setting is currently not applied as it does not offer a visible effect at the moment */} + {/* + }/> + */} + + )} + MemberPowerTag; diff --git a/src/app/pages/ThemeManager.tsx b/src/app/pages/ThemeManager.tsx index 69d50cdb9c..b9e7de6c4d 100644 --- a/src/app/pages/ThemeManager.tsx +++ b/src/app/pages/ThemeManager.tsx @@ -10,6 +10,7 @@ import { } from '../hooks/useTheme'; import { useSetting } from '../state/hooks/settings'; import { settingsAtom } from '../state/settings'; +import { hexToGrayscale } from '../plugins/colorUtils'; export function UnAuthRouteThemeManager() { const systemThemeKind = useSystemThemeKind(); @@ -35,7 +36,6 @@ export function AuthRouteThemeManager({ children }: { children: ReactNode }) { useEffect(() => { document.body.className = ''; document.body.classList.add(configClass, varsClass); - document.body.classList.add(...activeTheme.classNames); if (monochromeMode) { @@ -45,5 +45,55 @@ export function AuthRouteThemeManager({ children }: { children: ReactNode }) { } }, [activeTheme, monochromeMode]); - return {children}; + return ( + + + {children} + + ); +} + +export function CustomThemeManager() { + const [customBackgroundEnabled] = useSetting(settingsAtom, 'customBackgroundEnabled'); + const [customBgColor1] = useSetting(settingsAtom, 'customBgColor1'); + const [customBgColor2] = useSetting(settingsAtom, 'customBgColor2'); + const [customBgColor3] = useSetting(settingsAtom, 'customBgColor3'); + const [customBgColor4] = useSetting(settingsAtom, 'customBgColor4'); + const [customBgColor5] = useSetting(settingsAtom, 'customBgColor5'); + const [transparency] = useSetting(settingsAtom, 'transparency'); + const [angle] = useSetting(settingsAtom, 'angle'); + const [blur] = useSetting(settingsAtom, 'blur'); + + const [monochromeMode] = useSetting(settingsAtom, 'monochromeMode'); + + useEffect(() => { + if (!customBackgroundEnabled) { + document.body.style.setProperty('--custom-gradient', 'none'); + document.body.style.opacity = '1'; + document.body.style.backdropFilter = `blur(0px)`; + return; + } + + console.log("Monochrome mode:", monochromeMode); + + const colors = [customBgColor1, customBgColor2, customBgColor3, customBgColor4, customBgColor5] + .filter(Boolean) + .map(c => (monochromeMode ? hexToGrayscale(c) : c)) + .join(', '); + + const gradient = `linear-gradient(${angle}deg, ${colors})`; + document.body.style.background = gradient; + + console.log(colors); + + if (blur && blur > 0) { + document.body.style.backdropFilter = `blur(${blur}px)`; + } + + if (transparency !== undefined) { + document.body.style.opacity = `${1 - transparency / 100}`; + } + }, [customBackgroundEnabled, customBgColor1, customBgColor2, customBgColor3, customBgColor4, customBgColor5, transparency, angle, blur, monochromeMode]); + + return null; } diff --git a/src/app/plugins/color.ts b/src/app/plugins/color.ts deleted file mode 100644 index 47c73170f8..0000000000 --- a/src/app/plugins/color.ts +++ /dev/null @@ -1,16 +0,0 @@ -import chroma from 'chroma-js'; -import { ThemeKind } from '../hooks/useTheme'; - -export const accessibleColor = (themeKind: ThemeKind, color: string): string => { - if (!chroma.valid(color)) return color; - - let lightness = chroma(color).lab()[0]; - if (themeKind === ThemeKind.Dark && lightness < 60) { - lightness = 60; - } - if (themeKind === ThemeKind.Light && lightness > 50) { - lightness = 50; - } - - return chroma(color).set('lab.l', lightness).hex(); -}; diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index 31ee6ccb97..ee6c4a107a 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -15,6 +15,15 @@ export interface Settings { lightThemeId?: string; darkThemeId?: string; monochromeMode?: boolean; + customBackgroundEnabled: boolean; + transparency: number; + blur: number; + angle: number; + customBgColor1: string, + customBgColor2: string, + customBgColor3: string, + customBgColor4: string, + customBgColor5: string, isMarkdown: boolean; editorToolbar: boolean; twitterEmoji: boolean; @@ -48,7 +57,17 @@ const defaultSettings: Settings = { useSystemTheme: true, lightThemeId: undefined, darkThemeId: undefined, + monochromeMode: false, + customBackgroundEnabled: false, + transparency: 0.5, + blur: 20, + angle: 45, + customBgColor1: '#6600ff', + customBgColor2: '#ff0000', + customBgColor3: '#00ff11', + customBgColor4: '#00eaff', + customBgColor5: '#ddff00', isMarkdown: true, editorToolbar: false, twitterEmoji: false, From 42276c4f0c0d656b78d2e0e23ad6a4d3526596c6 Mon Sep 17 00:00:00 2001 From: LilyLavenderPony Date: Thu, 26 Mar 2026 10:15:57 +0100 Subject: [PATCH 3/8] Changed default settings and removed unnecessary code --- src/app/components/nav/styles.css.ts | 1 - src/app/components/page/Page.tsx | 3 +-- src/app/components/sidebar/Sidebar.css.ts | 13 ------------- src/app/features/settings/general/General.tsx | 2 -- src/app/state/settings.ts | 1 - 5 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/app/components/nav/styles.css.ts b/src/app/components/nav/styles.css.ts index e1e1632190..48b2a4d344 100644 --- a/src/app/components/nav/styles.css.ts +++ b/src/app/components/nav/styles.css.ts @@ -81,7 +81,6 @@ const NavItemBase = style({ }, }, }); - export const NavItem = recipe({ base: [DefaultReset, NavItemBase, Disabled], variants: { diff --git a/src/app/components/page/Page.tsx b/src/app/components/page/Page.tsx index e6140b796c..a54563855c 100644 --- a/src/app/components/page/Page.tsx +++ b/src/app/components/page/Page.tsx @@ -14,8 +14,7 @@ export function PageRoot({ nav, children }: PageRootProps) { const screenSize = useScreenSizeContext(); return ( - //{classNames(ContainerColor({ variant: 'Background' }), backgroundFrontLayer)} - + {nav} {screenSize !== ScreenSize.Mobile && ( diff --git a/src/app/components/sidebar/Sidebar.css.ts b/src/app/components/sidebar/Sidebar.css.ts index c67bbdbd98..c36862232b 100644 --- a/src/app/components/sidebar/Sidebar.css.ts +++ b/src/app/components/sidebar/Sidebar.css.ts @@ -16,19 +16,6 @@ export const Sidebar = style([ }, ]); -export const SidebarFrontLayer = style([ - DefaultReset, - { - width: toRem(66), - backgroundColor: color.Background.Container, - borderRight: `${config.borderWidth.B300} solid ${color.Background.ContainerLine}`, - - display: 'flex', - flexDirection: 'column', - color: color.Background.OnContainer, - }, -]); - export const SidebarStack = style([ DefaultReset, { diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 29c1ba38ce..5ecc2ef10d 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -53,8 +53,6 @@ import { useMessageLayoutItems } from '../../../hooks/useMessageLayout'; import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing'; import { useDateFormatItems } from '../../../hooks/useDateFormat'; import { SequenceCardStyle } from '../styles.css'; -import { transcode } from 'buffer'; -import { current } from 'immer'; type ThemeSelectorProps = { themeNames: Record; diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index ee6c4a107a..cbd2ae96fa 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -57,7 +57,6 @@ const defaultSettings: Settings = { useSystemTheme: true, lightThemeId: undefined, darkThemeId: undefined, - monochromeMode: false, customBackgroundEnabled: false, transparency: 0.5, From dbab126df269f923ecf4cffe5908657c7022da79 Mon Sep 17 00:00:00 2001 From: LilyLavenderPony Date: Thu, 26 Mar 2026 10:34:36 +0100 Subject: [PATCH 4/8] Changed default value for blur --- src/app/state/settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index cbd2ae96fa..37bfe11661 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -60,7 +60,7 @@ const defaultSettings: Settings = { monochromeMode: false, customBackgroundEnabled: false, transparency: 0.5, - blur: 20, + blur: 0, angle: 45, customBgColor1: '#6600ff', customBgColor2: '#ff0000', From b0e4867aef07fc28f312283c5b298034ca1c49aa Mon Sep 17 00:00:00 2001 From: LilyLavenderPony Date: Thu, 26 Mar 2026 12:16:04 +0100 Subject: [PATCH 5/8] Further limited maximum transparency level --- src/app/pages/ThemeManager.tsx | 8 ++------ src/app/state/settings.ts | 2 +- src/index.css | 1 + 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/app/pages/ThemeManager.tsx b/src/app/pages/ThemeManager.tsx index b9e7de6c4d..c4020a49b2 100644 --- a/src/app/pages/ThemeManager.tsx +++ b/src/app/pages/ThemeManager.tsx @@ -68,14 +68,12 @@ export function CustomThemeManager() { useEffect(() => { if (!customBackgroundEnabled) { - document.body.style.setProperty('--custom-gradient', 'none'); + document.body.style.background = 'none'; document.body.style.opacity = '1'; - document.body.style.backdropFilter = `blur(0px)`; + document.body.style.backdropFilter = 'none'; return; } - console.log("Monochrome mode:", monochromeMode); - const colors = [customBgColor1, customBgColor2, customBgColor3, customBgColor4, customBgColor5] .filter(Boolean) .map(c => (monochromeMode ? hexToGrayscale(c) : c)) @@ -83,8 +81,6 @@ export function CustomThemeManager() { const gradient = `linear-gradient(${angle}deg, ${colors})`; document.body.style.background = gradient; - - console.log(colors); if (blur && blur > 0) { document.body.style.backdropFilter = `blur(${blur}px)`; diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index 37bfe11661..d00475e1fa 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -59,7 +59,7 @@ const defaultSettings: Settings = { darkThemeId: undefined, monochromeMode: false, customBackgroundEnabled: false, - transparency: 0.5, + transparency: 15, blur: 0, angle: 45, customBgColor1: '#6600ff', diff --git a/src/index.css b/src/index.css index ca28536d22..2716d9d474 100644 --- a/src/index.css +++ b/src/index.css @@ -54,6 +54,7 @@ body { /*Why font-variant-ligatures => https://github.com/rsms/inter/issues/222 */ font-variant-ligatures: no-contextual; } + #root { width: 100%; height: 100%; From 8c568bd2fdc3094c13f60b0c68d5653e05f8ee82 Mon Sep 17 00:00:00 2001 From: LilyLavenderPony Date: Thu, 26 Mar 2026 15:12:16 +0100 Subject: [PATCH 6/8] Assign noticeable transparency value on custom-theme activation if the initial value is zero --- src/app/features/settings/general/General.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 5ecc2ef10d..63d336ba1c 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -365,7 +365,9 @@ function Appearance() { const [systemTheme, setSystemTheme] = useSetting(settingsAtom, 'useSystemTheme'); const [monochromeMode, setMonochromeMode] = useSetting(settingsAtom, 'monochromeMode'); const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); - const [customBackgroundEnabled, setCustomBackgroundEnabled] = useSetting(settingsAtom,'customBackgroundEnabled'); + + const [customBackgroundEnabled, setCustomBackgroundEnabled] = useSetting(settingsAtom, 'customBackgroundEnabled'); + const [transparency, setTransparency] = useSetting(settingsAtom, 'transparency'); const [customBgColor1, setCustomBgColor1] = useSetting(settingsAtom, 'customBgColor1'); const [customBgColor2, setCustomBgColor2] = useSetting(settingsAtom, 'customBgColor2'); const [customBgColor3, setCustomBgColor3] = useSetting(settingsAtom, 'customBgColor3'); @@ -399,13 +401,16 @@ function Appearance() { { + setCustomBackgroundEnabled(value); + if(value && transparency == 0) setTransparency(15) + }} /> } /> From c694c142b8ebd0015cedfced3bda6f77547b8d93 Mon Sep 17 00:00:00 2001 From: LilyLavenderPony Date: Fri, 27 Mar 2026 11:13:15 +0100 Subject: [PATCH 7/8] Added option to display gradient on background only --- index.html | 3 +- src/app/features/settings/general/General.tsx | 14 ++++++ src/app/pages/ThemeManager.tsx | 48 +++++++++++++------ src/app/state/settings.ts | 2 + src/index.css | 9 ++++ 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/index.html b/index.html index 2eefeee2cd..bf9e914ba5 100644 --- a/index.html +++ b/index.html @@ -85,12 +85,13 @@ href="./public/res/apple/apple-touch-icon-180x180.png" /> - +
+
diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 63d336ba1c..8654396fa8 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -367,6 +367,7 @@ function Appearance() { const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); const [customBackgroundEnabled, setCustomBackgroundEnabled] = useSetting(settingsAtom, 'customBackgroundEnabled'); + const [customBackgroundOnly, setCustomBackgroundOnly] = useSetting(settingsAtom, 'customBackgroundOnly'); const [transparency, setTransparency] = useSetting(settingsAtom, 'transparency'); const [customBgColor1, setCustomBgColor1] = useSetting(settingsAtom, 'customBgColor1'); const [customBgColor2, setCustomBgColor2] = useSetting(settingsAtom, 'customBgColor2'); @@ -448,6 +449,19 @@ function Appearance() {
+ + + } + /> + + }/> diff --git a/src/app/pages/ThemeManager.tsx b/src/app/pages/ThemeManager.tsx index c4020a49b2..2ace441d5a 100644 --- a/src/app/pages/ThemeManager.tsx +++ b/src/app/pages/ThemeManager.tsx @@ -1,5 +1,5 @@ import React, { ReactNode, useEffect } from 'react'; -import { configClass, varsClass } from 'folds'; +import { config, configClass, varsClass } from 'folds'; import { DarkTheme, LightTheme, @@ -55,6 +55,7 @@ export function AuthRouteThemeManager({ children }: { children: ReactNode }) { export function CustomThemeManager() { const [customBackgroundEnabled] = useSetting(settingsAtom, 'customBackgroundEnabled'); + const [customBackgroundOnly] = useSetting(settingsAtom, 'customBackgroundOnly'); const [customBgColor1] = useSetting(settingsAtom, 'customBgColor1'); const [customBgColor2] = useSetting(settingsAtom, 'customBgColor2'); const [customBgColor3] = useSetting(settingsAtom, 'customBgColor3'); @@ -63,33 +64,52 @@ export function CustomThemeManager() { const [transparency] = useSetting(settingsAtom, 'transparency'); const [angle] = useSetting(settingsAtom, 'angle'); const [blur] = useSetting(settingsAtom, 'blur'); - const [monochromeMode] = useSetting(settingsAtom, 'monochromeMode'); useEffect(() => { + const gradientLayer = document.getElementById('gradientLayer'); + if (!gradientLayer) return; + + gradientLayer.style.setProperty('z-index', customBackgroundOnly ? config.zIndex.Z100 : config.zIndex.Max); + if (!customBackgroundEnabled) { - document.body.style.background = 'none'; - document.body.style.opacity = '1'; - document.body.style.backdropFilter = 'none'; + gradientLayer.style.background = 'none'; + gradientLayer.style.opacity = '0'; + gradientLayer.style.backdropFilter = 'none'; return; } const colors = [customBgColor1, customBgColor2, customBgColor3, customBgColor4, customBgColor5] .filter(Boolean) - .map(c => (monochromeMode ? hexToGrayscale(c) : c)) + .map((c) => (monochromeMode ? hexToGrayscale(c) : c)) .join(', '); - - const gradient = `linear-gradient(${angle}deg, ${colors})`; - document.body.style.background = gradient; - + + gradientLayer.style.background = `linear-gradient(${angle}deg, ${colors})`; + if (blur && blur > 0) { - document.body.style.backdropFilter = `blur(${blur}px)`; + gradientLayer.style.backdropFilter = `blur(${blur}px)`; + } else { + gradientLayer.style.backdropFilter = 'none'; } - + if (transparency !== undefined) { - document.body.style.opacity = `${1 - transparency / 100}`; + gradientLayer.style.opacity = `${transparency / 100}`; + } else { + gradientLayer.style.opacity = '0'; } - }, [customBackgroundEnabled, customBgColor1, customBgColor2, customBgColor3, customBgColor4, customBgColor5, transparency, angle, blur, monochromeMode]); + }, [ + customBackgroundEnabled, + customBackgroundOnly, + customBgColor1, + customBgColor2, + customBgColor3, + customBgColor4, + customBgColor5, + transparency, + angle, + blur, + monochromeMode, + ]); return null; } diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index d00475e1fa..7ab20ab7f3 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -16,6 +16,7 @@ export interface Settings { darkThemeId?: string; monochromeMode?: boolean; customBackgroundEnabled: boolean; + customBackgroundOnly: boolean; transparency: number; blur: number; angle: number; @@ -59,6 +60,7 @@ const defaultSettings: Settings = { darkThemeId: undefined, monochromeMode: false, customBackgroundEnabled: false, + customBackgroundOnly: false, transparency: 15, blur: 0, angle: 45, diff --git a/src/index.css b/src/index.css index 2716d9d474..26d3e04b41 100644 --- a/src/index.css +++ b/src/index.css @@ -62,6 +62,15 @@ body { flex-direction: column; } +#gradientLayer { + position: fixed; + inset: 0; + pointer-events: none; + opacity: 0; + background: none; + backdrop-filter: none; +} + *, *::before, *::after { From a1a58dc613588f3d98d0041202014f03a246d7d6 Mon Sep 17 00:00:00 2001 From: LilyLavenderPony Date: Sat, 28 Mar 2026 16:07:44 +0100 Subject: [PATCH 8/8] Fixed gradient not disappearing when no colors selected --- src/app/features/settings/general/General.tsx | 15 ++++------ src/app/pages/ThemeManager.tsx | 28 ++++++------------- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 8654396fa8..506cfa3f05 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -329,15 +329,12 @@ function PageZoomInput() { return ; } -function ColorPickerButton({ - color, - onChange, - onRemove, -}: { - color?: string; +type ColorPickerButtonProps = { + color: string; onChange: (color: string) => void; onRemove?: () => void; -}) { +} +function ColorPickerButton({ color, onChange, onRemove, }: ColorPickerButtonProps) { return ( } @@ -351,7 +348,7 @@ function ColorPickerButton({ type="button" variant="Secondary" fill="Soft" - radii="300" + radii="500" style={{ flex: 1 }} > @@ -451,7 +448,7 @@ function Appearance() { (monochromeMode ? hexToGrayscale(c) : c)) .join(', '); - gradientLayer.style.background = `linear-gradient(${angle}deg, ${colors})`; - - if (blur && blur > 0) { - gradientLayer.style.backdropFilter = `blur(${blur}px)`; - } else { + if (!customBackgroundEnabled || !colors) { + gradientLayer.style.background = 'none'; + gradientLayer.style.opacity = '0'; gradientLayer.style.backdropFilter = 'none'; + return; } - if (transparency !== undefined) { - gradientLayer.style.opacity = `${transparency / 100}`; - } else { - gradientLayer.style.opacity = '0'; - } + gradientLayer.style.setProperty('z-index', customBackgroundOnly ? config.zIndex.Z100 : config.zIndex.Max); + gradientLayer.style.background = `linear-gradient(${angle}deg, ${colors})`; + gradientLayer.style.backdropFilter = blur > 0 ? `blur(${blur}px)` : 'none'; + gradientLayer.style.opacity = `${(transparency ?? 0) / 100}`; + }, [ customBackgroundEnabled, customBackgroundOnly,