# Plan: Add Dark/Light Mode with Dynamic Favicons **Priority:** MEDIUM **Dependencies:** None **Affects:** User experience, accessibility, branding ## Overview Add full dark mode / light mode support throughout the app, including: - Dynamic theme switching - Persistent user preference - System preference detection - Mode-specific favicons - Smooth transitions ## Current State - App uses a minimal grayscale theme - No dark mode support - Single favicon for all modes - No theme toggle UI ## Proposed Implementation ### 1. Mantine Color Scheme Support Mantine has built-in dark mode support. We'll leverage `@mantine/core` ColorSchemeScript and hooks. #### Update `app/layout.tsx` ```typescript import { ColorSchemeScript, MantineProvider, } from '@mantine/core'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {/* Dynamic favicon based on theme */} {children} ); } ``` ### 2. Update Theme Configuration #### `app/theme.ts` ```typescript import { createTheme, MantineColorsTuple } from '@mantine/core'; const gray: MantineColorsTuple = [ '#f8f9fa', '#f1f3f5', '#e9ecef', '#dee2e6', '#ced4da', '#adb5bd', '#868e96', '#495057', '#343a40', '#212529', ]; export const theme = createTheme({ fontFamily: 'Inter, system-ui, sans-serif', primaryColor: 'gray', colors: { gray, }, // Dark mode specific colors black: '#0a0a0a', white: '#ffffff', // Component-specific dark mode overrides components: { AppShell: { styles: (theme) => ({ main: { backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.white, }, }), }, Paper: { styles: (theme) => ({ root: { backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white, borderColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[3], }, }), }, Modal: { styles: (theme) => ({ content: { backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white, }, header: { backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0], }, }), }, }, }); ``` ### 3. Theme Toggle Component #### `components/ThemeToggle.tsx` ```typescript 'use client'; import { ActionIcon, useMantineColorScheme, useComputedColorScheme } from '@mantine/core'; import { IconSun, IconMoon } from '@tabler/icons-react'; export function ThemeToggle() { const { setColorScheme } = useMantineColorScheme(); const computedColorScheme = useComputedColorScheme('light'); return ( setColorScheme(computedColorScheme === 'dark' ? 'light' : 'dark')} variant="subtle" size="lg" aria-label="Toggle color scheme" > {computedColorScheme === 'dark' ? ( ) : ( )} ); } ``` ### 4. Add Toggle to Desktop Navigation #### `components/DesktopNav.tsx` ```typescript import { ThemeToggle } from './ThemeToggle'; export function DesktopNav() { return ( {/* Navigation items */} {/* Profile menu */} ); } ``` ### 5. Create Mode-Specific Favicons #### Design Requirements - **Light mode favicon:** Dark icon on transparent background - **Dark mode favicon:** Light icon on transparent background - Both should be SVG for scalability - Simple, recognizable icon representing "thoughts" or "ponderants" #### `public/favicon-light.svg` ```svg ``` #### `public/favicon-dark.svg` ```svg ``` ### 6. Update Galaxy Visualization for Dark Mode The 3D galaxy needs to look good in both modes: #### `components/galaxy/ThoughtGalaxy.tsx` ```typescript 'use client'; import { useComputedColorScheme } from '@mantine/core'; export function ThoughtGalaxy() { const colorScheme = useComputedColorScheme('light'); const isDark = colorScheme === 'dark'; return ( {/* Node spheres - adjust color based on theme */} {/* Link lines */} ); } ``` ### 7. Persist User Preference Mantine automatically stores the preference in localStorage with the key `mantine-color-scheme-value`. For server-side rendering, we can add a cookie fallback: #### `middleware.ts` ```typescript import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const response = NextResponse.next(); // Set color scheme cookie if not present if (!request.cookies.has('mantine-color-scheme')) { response.cookies.set('mantine-color-scheme', 'auto'); } return response; } ``` ## Testing Strategy ### Manual Playwright MCP Testing #### Test 1: Theme Toggle ```typescript test('User can toggle between light and dark mode', async ({ page }) => { await page.goto('/'); // Check initial theme const initialTheme = await page.evaluate(() => { return document.documentElement.getAttribute('data-mantine-color-scheme'); }); // Click theme toggle await page.click('[aria-label="Toggle color scheme"]'); // Check theme changed const newTheme = await page.evaluate(() => { return document.documentElement.getAttribute('data-mantine-color-scheme'); }); expect(newTheme).not.toBe(initialTheme); }); ``` #### Test 2: Theme Persistence ```typescript test('Theme preference persists across page refreshes', async ({ page }) => { await page.goto('/'); // Set to dark mode await page.click('[aria-label="Toggle color scheme"]'); await page.waitForTimeout(100); const darkTheme = await page.evaluate(() => { return document.documentElement.getAttribute('data-mantine-color-scheme'); }); // Refresh page await page.reload(); // Check theme persisted const persistedTheme = await page.evaluate(() => { return document.documentElement.getAttribute('data-mantine-color-scheme'); }); expect(persistedTheme).toBe(darkTheme); }); ``` #### Test 3: Favicon Changes ```typescript test('Favicon changes with theme', async ({ page }) => { await page.goto('/'); // Get favicon in light mode const lightFavicon = await page.evaluate(() => { const link = document.querySelector('link[rel="icon"]'); return link?.getAttribute('href'); }); // Switch to dark mode await page.click('[aria-label="Toggle color scheme"]'); await page.waitForTimeout(100); // Get favicon in dark mode const darkFavicon = await page.evaluate(() => { const link = document.querySelector('link[rel="icon"]'); return link?.getAttribute('href'); }); expect(lightFavicon).toContain('light'); expect(darkFavicon).toContain('dark'); }); ``` ### Magnitude Tests ```typescript import { test } from 'magnitude-test'; test('User can switch between light and dark mode', async (agent) => { await agent.open('http://localhost:3000'); await agent.check('Page loads in default theme'); await agent.act('Click theme toggle button'); await agent.check('Theme switches to opposite mode'); await agent.check('All UI elements are visible in new theme'); }); test('Dark mode looks good throughout app', async (agent) => { await agent.open('http://localhost:3000'); await agent.act('Switch to dark mode'); await agent.act('Navigate to /chat'); await agent.check('Chat interface is readable in dark mode'); await agent.act('Navigate to /galaxy'); await agent.check('Galaxy visualization looks good in dark mode'); await agent.act('Navigate to /edit'); await agent.check('Editor is readable in dark mode'); }); test('Theme preference persists', async (agent) => { await agent.open('http://localhost:3000'); await agent.act('Switch to dark mode'); await agent.check('App is in dark mode'); await agent.act('Refresh page'); await agent.check('App remains in dark mode'); }); ``` ## Implementation Steps 1. **Update Mantine provider and layout** - Add ColorSchemeScript to head - Add dynamic favicon links - Update MantineProvider with defaultColorScheme 2. **Update theme configuration** - Add dark mode color overrides - Update component styles for both modes 3. **Create ThemeToggle component** - Add sun/moon icon toggle - Wire up to Mantine color scheme hooks 4. **Add toggle to navigation** - Add to DesktopNav - Add to mobile nav if applicable 5. **Create favicons** - Design light mode favicon (dark icon) - Design dark mode favicon (light icon) - Export as SVG - Add to public folder 6. **Update galaxy visualization** - Add theme-aware colors - Test in both modes 7. **Test with Playwright MCP** - Test toggle functionality - Test persistence - Test favicon changes - Test all pages in both modes 8. **Add Magnitude tests** - Write comprehensive theme tests - Ensure all pass 9. **Commit and push (NO DEPLOY)** ## Success Criteria - ✅ User can toggle between light and dark mode - ✅ Theme preference persists across sessions - ✅ Favicon changes based on active theme - ✅ All UI elements are readable in both modes - ✅ Galaxy visualization looks good in both modes - ✅ System preference is detected and respected - ✅ Smooth transitions between modes - ✅ All Playwright MCP tests pass - ✅ All Magnitude tests pass ## Files to Create 1. `components/ThemeToggle.tsx` - Toggle component 2. `public/favicon-light.svg` - Light mode favicon 3. `public/favicon-dark.svg` - Dark mode favicon 4. `tests/playwright/theme.spec.ts` - Manual tests 5. `tests/magnitude/theme.mag.ts` - Magnitude tests ## Files to Update 1. `app/layout.tsx` - Add ColorSchemeScript and dynamic favicons 2. `app/theme.ts` - Add dark mode color overrides 3. `components/DesktopNav.tsx` - Add ThemeToggle 4. `components/galaxy/ThoughtGalaxy.tsx` - Add theme-aware colors