feat(frontend): marketing site, theme, wordmark, and UI polish#465
feat(frontend): marketing site, theme, wordmark, and UI polish#465skylerwww wants to merge 1 commit intoOpenSecretCloud:masterfrom
Conversation
- Add Maple wordmark component and brand assets; sidebar and chat chrome tweaks - Theme context, marketing home and solutions routes, agent/research pages - Shadcn/ui and dialog styling updates across account, billing, and team flows - Research hero: Manrope, smaller heading, descender-safe gradient text - Trim unused Bitcount font from index.html Made-with: Cursor
| @@ -1,5 +1,5 @@ | |||
| <!doctype html> | |||
| <html lang="en" style="color-scheme: light dark"> | |||
| <html lang="en"> | |||
There was a problem hiding this comment.
🔴 Dark mode flash-of-wrong-theme (FOUC) for users with dark OS preference
The PR removes the CSS @media (prefers-color-scheme: dark) rule from frontend/src/index.css (old lines 92-155) and the style="color-scheme: light dark" attribute from frontend/index.html:2, replacing them with a class-based .dark selector that depends on ThemeContext adding a .dark class via useEffect. Since useEffect runs only after React mounts, there is a window between initial page paint and React hydration where dark-mode users will see the light theme flash. The old media-query approach applied dark styles instantly without any JavaScript. Tailwind's darkMode: "class" (frontend/tailwind.config.js:9) also means all dark: utility variants now require the .dark class to be present, compounding the issue. The standard fix is an inline <script> in <head> that reads localStorage and applies the .dark class synchronously before any content paints.
Prompt for agents
The PR replaced CSS media-query-based dark mode with a class-based approach (ThemeContext adds .dark class via useEffect after React mount). This creates a flash-of-wrong-theme for dark mode users because the .dark class isn't applied until JavaScript executes.
To fix this, add a small inline script in the <head> of frontend/index.html (before any stylesheets or the main bundle) that synchronously reads the theme preference from localStorage (key: maple-theme) and applies the .dark class and color-scheme style to the <html> element. Something like:
<script>
(function() {
var stored = localStorage.getItem('maple-theme');
var isDark = stored === 'dark' || (stored !== 'light' && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (isDark) {
document.documentElement.classList.add('dark');
document.documentElement.style.colorScheme = 'dark';
} else {
document.documentElement.style.colorScheme = 'light';
}
})();
</script>
This must run before any CSS is evaluated to prevent the flash. The ThemeContext (frontend/src/contexts/ThemeContext.tsx) already uses the same localStorage key (maple-theme) and the same logic, so this inline script just front-runs it.
The relevant files are:
- frontend/index.html (add the inline script in <head>)
- frontend/src/contexts/ThemeContext.tsx (ensure STORAGE_KEY matches)
- frontend/src/index.css (the .dark block at line 161 needs the class applied early)
Was this helpful? React with 👍 or 👎 to provide feedback.
| return "system"; | ||
| }); | ||
|
|
||
| const resolvedTheme = theme === "system" ? getSystemTheme() : theme; |
There was a problem hiding this comment.
🟡 ThemeContext resolvedTheme becomes stale when OS theme changes in "system" mode
resolvedTheme at frontend/src/contexts/ThemeContext.tsx:28 is a derived value computed during render, not React state. When theme === "system" and the OS preference changes, the media query handler (lines 51-59) correctly updates the DOM (adds/removes .dark class), but never calls setState, so resolvedTheme in the context remains stale until something else triggers a re-render. Any current or future consumer reading resolvedTheme from useTheme() will get an incorrect value after an OS theme switch. The fix is to store the resolved theme in state and update it from the media query handler.
Prompt for agents
In frontend/src/contexts/ThemeContext.tsx, the resolvedTheme on line 28 is computed as a plain derived value during render:
const resolvedTheme = theme === 'system' ? getSystemTheme() : theme;
This doesn't update when the OS theme changes because the media query handler (lines 51-59) manipulates the DOM directly without triggering a React state update.
Fix: make resolvedTheme a state variable. For example:
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>(() =>
theme === 'system' ? getSystemTheme() : theme
);
Then in the media query change handler, call setResolvedTheme(mediaQuery.matches ? 'dark' : 'light'). Also update resolvedTheme when theme changes (e.g. in a useEffect dependent on theme).
This ensures that any component consuming resolvedTheme from the context gets an up-to-date value after an OS theme switch.
Was this helpful? React with 👍 or 👎 to provide feedback.
Detailed spec derived from designer PR #465, scoped to the authenticated product UI only. Covers design system tokens, theme architecture, component-by-component visual changes, and engineering standards for reimplementation. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Detailed spec derived from designer PR #465, scoped to the authenticated product UI only. Covers design system tokens, theme architecture, component-by-component visual changes, and engineering standards for reimplementation. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Summary
index.htmlNotes
Pushed from fork
skylerwww/Maple— please review and merge when ready.Made with Cursor