diff --git a/fonts.css b/fonts.css
index 3eeefe4ffd6d3..d9b8c1b7b1b13 100644
--- a/fonts.css
+++ b/fonts.css
@@ -1,24 +1,40 @@
+/* FOUC fix — CSS loads before JS, so dark bg is set immediately */
+html {
+ background-color: #121212;
+}
+
+html[data-theme="light"],
+html[data-theme="light"] body {
+ background-color: #FFFFFF;
+}
+
+html[data-theme="dark"],
+html[data-theme="dark"] body {
+ background-color: #121212;
+}
+
+body {
+ background-color: var(--background, #121212);
+}
+
@font-face {
font-family: "Qanelas Soft Black";
src: url('./static/fonts/qanelas-soft/QanelasSoftBlack.otf') format('opentype');
font-weight: normal;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Black Italic";
src: url('./static/fonts/qanelas-soft/QanelasSoftBlackItalic.otf') format('opentype');
font-style: italic;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Heavy";
src: url('./static/fonts/qanelas-soft/QanelasSoftHeavy.otf') format('opentype');
font-weight: 900;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Heavy Italic";
src: url('./static/fonts/qanelas-soft/QanelasSoftHeavyItalic.otf') format('opentype');
@@ -26,14 +42,12 @@
font-style: italic;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft ExtraBold";
src: url('./static/fonts/qanelas-soft/QanelasSoftExtraBold.otf') format('opentype');
font-weight: 800;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft ExtraBold Italic";
src: url('./static/fonts/qanelas-soft/QanelasSoftExtraBoldItalic.otf') format('opentype');
@@ -41,14 +55,12 @@
font-style: italic;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Bold";
src: url('./static/fonts/qanelas-soft/QanelasSoftBold.otf') format('opentype');
font-weight: bold;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Bold Italic";
src: url('./static/fonts/qanelas-soft/QanelasSoftBoldItalic.otf') format('opentype');
@@ -56,14 +68,12 @@
font-style: italic;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft";
src: url('./static/fonts/qanelas-soft/QanelasSoftSemiBold.otf') format('opentype');
font-weight: 600;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft SemiBold Italic";
src: url('./static/fonts/qanelas-soft/QanelasSoftSemiBoldItalic.otf') format('opentype');
@@ -71,14 +81,12 @@
font-style: italic;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Medium";
src: url('./static/fonts/qanelas-soft/QanelasSoftMedium.otf') format('opentype');
font-weight: 500;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Medium Italic";
src: url('./static/fonts/qanelas-soft/QanelasSoftMediumItalic.otf') format('opentype');
@@ -86,14 +94,12 @@
font-style: italic;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft";
src: url('./static/fonts/qanelas-soft/QanelasSoftRegular.otf') format('opentype');
font-weight: 400;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Regular Italic";
src: url('./static/fonts/qanelas-soft/QanelasSoftRegularItalic.otf') format('opentype');
@@ -101,14 +107,12 @@
font-style: italic;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Light";
src: url('./static/fonts/qanelas-soft/QanelasSoftLight.otf') format('opentype');
font-weight: 300;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Light Italic";
src: url('./static/fonts/qanelas-soft/QanelasSoftLightItalic.otf') format('opentype');
@@ -116,14 +120,12 @@
font-style: italic;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft UltraLight";
src: url('./static/fonts/qanelas-soft/QanelasSoftUltraLight.otf') format('opentype');
font-weight: 200;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft UltraLight Italic";
src: url('./static/fonts/qanelas-soft/QanelasSoftUltraLightItalic.otf') format('opentype');
@@ -131,14 +133,12 @@
font-style: italic;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Thin";
src: url('./static/fonts/qanelas-soft/QanelasSoftThin.otf') format('opentype');
font-weight: 100;
font-display: swap;
}
-
@font-face {
font-family: "Qanelas Soft Thin Italic";
src: url('./static/fonts/qanelas-soft/QanelasSoftThinItalic.otf') format('opentype');
@@ -153,7 +153,6 @@
font-display: swap;
src: url("./static/fonts/open-sans/OpenSans-Regular.ttf") format("truetype");
}
-
@font-face {
font-family: "Open Sans";
font-style: normal;
@@ -161,11 +160,10 @@
font-display: swap;
src: url("./static/fonts/open-sans/OpenSans-SemiBold.ttf") format("truetype");
}
-
@font-face {
font-family: "Open Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("./static/fonts/open-sans/OpenSans-Bold.ttf") format("truetype");
-}
\ No newline at end of file
+}
diff --git a/onRenderBody.js b/onRenderBody.js
index 370709d6c1ceb..ae9ce074ef6ff 100644
--- a/onRenderBody.js
+++ b/onRenderBody.js
@@ -1,57 +1,44 @@
import React from "react";
-import { DarkThemeKey, ThemeSetting } from "./src/theme/app/ThemeManager.js";
-import lighttheme, { darktheme } from "./src/theme/app/themeStyles";
-const themes = { light: lighttheme, dark: darktheme };
-
-const MagicScriptTag = (props) => {
- const codeToRunOnClient = `
- (function() {
- // 1. Keeps SYSTEM as the priority preference
- const themeFromLocalStorage = localStorage.getItem('${DarkThemeKey}') || '${ThemeSetting.SYSTEM}';
-
- // 2. We change the check to look for LIGHT mode explicitly
- const systemLightModeSetting = () => window.matchMedia ? window.matchMedia('(prefers-color-scheme: light)') : null;
-
- const isLightModeActive = () => {
- return !!systemLightModeSetting()?.matches;
- };
-
- let colorMode;
- switch (themeFromLocalStorage) {
- case '${ThemeSetting.SYSTEM}':
- // LOGIC CHANGE: If Light is active -> Light. Otherwise (Dark, No Preference, or Error) -> Dark.
- colorMode = isLightModeActive() ? '${ThemeSetting.LIGHT}' : '${ThemeSetting.DARK}'
- break
- case '${ThemeSetting.DARK}':
- case '${ThemeSetting.LIGHT}':
- colorMode = themeFromLocalStorage
- break
- default:
- // 3. Fallback to DARK in case of error
- colorMode = '${ThemeSetting.DARK}'
- }
-
- const root = document.documentElement;
- const iterate = (obj) => {
- if (!obj) return;
- Object.keys(obj).forEach(key => {
- if (typeof obj[key] === 'object') {
- iterate(obj[key])
- } else {
- root.style.setProperty("--" + key, obj[key])
- }
- })
- }
- const parsedTheme = JSON.parse('${JSON.stringify(props.theme)}')
- const theme = parsedTheme[colorMode]
- iterate(theme)
- root.style.setProperty('--initial-color-mode', colorMode);
- })()
+export const onRenderBody = ({ setHeadComponents }) => {
+ const scriptContent = `
+ (function() {
+ try {
+ var stored = localStorage.getItem('theme');
+ var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+ var isDark = stored === 'dark' || (stored === null && prefersDark);
+ var bg = isDark ? '#121212' : '#FFFFFF';
+ document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
+ document.documentElement.style.backgroundColor = bg;
+ document.documentElement.style.setProperty('--initial-color-mode', isDark ? 'dark' : 'light');
+ } catch(e) {
+ document.documentElement.style.backgroundColor = '#121212';
+ }
+ document.documentElement.style.visibility = 'visible';
+ })();
`;
- return ;
-};
-export const onRenderBody = ( { setPreBodyComponents }) => {
- setPreBodyComponents();
+ setHeadComponents([
+ ,
+ ,
+ ]);
};
diff --git a/src/theme/app/StyledThemeProvider.js b/src/theme/app/StyledThemeProvider.js
index 5a396f3d18c68..4f31660818ba3 100644
--- a/src/theme/app/StyledThemeProvider.js
+++ b/src/theme/app/StyledThemeProvider.js
@@ -1,6 +1,3 @@
-//uses isDark state to choose styled-component theme (in themeStyles.js)
-//and use ThemeProvider to allow all styled components access to values via props.theme
-
import React, { useContext } from "react";
import { ThemeProvider } from "styled-components";
import { ThemeManagerContext } from "./ThemeManager";
@@ -12,11 +9,9 @@ export const StyledThemeProvider = (props) => {
const { children, darkTheme, lightTheme } = props;
const { isDark, didLoad } = useContext(ThemeManagerContext);
- // For SSR, we need to provide a consistent theme initially
- // This ensures the server and client render the same thing initially
const currentTheme = isDark ? darkTheme : lightTheme;
const theme = {
- ...(didLoad || !isBrowser ? currentTheme : transformTheme(currentTheme)),
+ ...(currentTheme),
};
return (
diff --git a/src/theme/app/ThemeManager.js b/src/theme/app/ThemeManager.js
index f3b71d625eedf..b96f6453ab6e5 100644
--- a/src/theme/app/ThemeManager.js
+++ b/src/theme/app/ThemeManager.js
@@ -1,7 +1,3 @@
-//majority of code from https://www.joshwcomeau.com/react/dark-mode/ and https://github.com/gperl27/gatsby-styled-components-dark-mode
-
-//context provider for app to make accessible theme setting, toggle function, etc.
-
import React, { createContext, useState, useEffect, useCallback } from "react";
export const ThemeSetting = {
@@ -22,11 +18,11 @@ const defaultState = {
export const ThemeManagerContext = createContext(defaultState);
-// Safe check for browser environment
const isBrowser = typeof window !== "undefined";
const systemDarkModeSetting = () =>
isBrowser && window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;
+
const isDarkModeActive = () => {
return !!systemDarkModeSetting()?.matches;
};
@@ -39,78 +35,59 @@ const applyThemeToDOM = (theme) => {
};
export const ThemeManagerProvider = (props) => {
- const [themeSetting, setThemeSetting] = useState(ThemeSetting.SYSTEM);
+ const [themeSetting, setThemeSetting] = useState(ThemeSetting.SYSTEM);
const [didLoad, setDidLoad] = useState(false);
- const [isDark, setIsDark] = useState(false);
+ const [isDark, setIsDark] = useState(false);
useEffect(() => {
if (!isBrowser) return;
-
- const root = window.document.documentElement;
- const initialColorValue = root.style.getPropertyValue("--initial-color-mode");
-
- // Get stored theme from localStorage
const storedTheme = localStorage.getItem(DarkThemeKey);
- if (storedTheme && storedTheme !== ThemeSetting.SYSTEM) {
- const isDarkTheme = storedTheme === ThemeSetting.DARK;
- setIsDark(isDarkTheme);
- setThemeSetting(storedTheme);
- applyThemeToDOM(storedTheme);
- } else if (initialColorValue) {
- setIsDark(initialColorValue === ThemeSetting.DARK);
- setThemeSetting(ThemeSetting.SYSTEM);
+ if (storedTheme === ThemeSetting.LIGHT) {
+ setIsDark(false);
+ setThemeSetting(ThemeSetting.LIGHT);
+ applyThemeToDOM(ThemeSetting.LIGHT);
+ } else if (storedTheme === ThemeSetting.DARK) {
+ setIsDark(true);
+ setThemeSetting(ThemeSetting.DARK);
+ applyThemeToDOM(ThemeSetting.DARK);
} else {
- // Fallback to system preference
const systemIsDark = isDarkModeActive();
setIsDark(systemIsDark);
- const theme = systemIsDark ? ThemeSetting.DARK : ThemeSetting.LIGHT;
- applyThemeToDOM(theme);
+ setThemeSetting(ThemeSetting.SYSTEM);
+ applyThemeToDOM(systemIsDark ? ThemeSetting.DARK : ThemeSetting.LIGHT);
}
setDidLoad(true);
}, []);
- // Listen to system color scheme changes only when on SYSTEM mode
useEffect(() => {
if (!isBrowser || themeSetting !== ThemeSetting.SYSTEM) return;
-
const darkModeMediaQuery = systemDarkModeSetting();
if (!darkModeMediaQuery) return;
-
const handleChange = (e) => {
setIsDark(e.matches);
applyThemeToDOM(e.matches ? ThemeSetting.DARK : ThemeSetting.LIGHT);
};
-
darkModeMediaQuery.addEventListener("change", handleChange);
return () => darkModeMediaQuery.removeEventListener("change", handleChange);
}, [themeSetting]);
const toggleDark = useCallback(() => {
if (!isBrowser) return;
-
const newIsDark = !isDark;
const newTheme = newIsDark ? ThemeSetting.DARK : ThemeSetting.LIGHT;
-
- // Update state
setIsDark(newIsDark);
setThemeSetting(newTheme);
-
- // Apply to DOM immediately
applyThemeToDOM(newTheme);
-
- // Persist to localStorage
localStorage.setItem(DarkThemeKey, newTheme);
}, [isDark]);
const changeThemeSetting = useCallback(
(setting) => {
if (!isBrowser) return;
-
let newIsDark = isDark;
let themeToApply = setting;
-
switch (setting) {
case ThemeSetting.SYSTEM: {
newIsDark = isDarkModeActive();
@@ -128,15 +105,9 @@ export const ThemeManagerProvider = (props) => {
default:
return;
}
-
- // Update state
setIsDark(newIsDark);
setThemeSetting(setting);
-
- // Apply to DOM immediately
applyThemeToDOM(themeToApply);
-
- // Persist to localStorage
localStorage.setItem(DarkThemeKey, setting);
},
[isDark]