From db7be428a204836a932f6ca8c3ee511d4e74c00e Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 6 Aug 2026 18:28:31 +0000 Subject: [PATCH 1/3] Add dark mode support - Introduce useTheme hook that manages light/dark theme state, persists the choice in localStorage, and respects the user's prefers-color-scheme by default. - Add a sun/moon toggle button in the Header to switch themes. - Add dark theme CSS variable overrides (activated via html[data-theme="dark"]) plus background/text color variables and smooth transitions so the whole app (board, input, banners) reacts to the theme. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/components/App/App.js | 5 +++- src/components/Header/Header.js | 37 +++++++++++++++++++++++++- src/hooks/useTheme.js | 40 ++++++++++++++++++++++++++++ src/styles.css | 47 +++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useTheme.js diff --git a/src/components/App/App.js b/src/components/App/App.js index ce0d236..5b59212 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.js @@ -1,10 +1,13 @@ import Game from '../Game'; import Header from '../Header'; +import useTheme from '../../hooks/useTheme'; function App() { + const [theme, toggleTheme] = useTheme(); + return (
-
+
diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index ce7c934..03c840c 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -1,9 +1,44 @@ import React from 'react'; -function Header() { +function Header({ theme, onToggleTheme }) { + const isDark = theme === 'dark'; + return (
+

Word Game

+
+ +
); } diff --git a/src/hooks/useTheme.js b/src/hooks/useTheme.js new file mode 100644 index 0000000..999805b --- /dev/null +++ b/src/hooks/useTheme.js @@ -0,0 +1,40 @@ +import { useEffect, useState } from 'react'; + +const STORAGE_KEY = 'word-game-theme'; + +function getInitialTheme() { + if (typeof window === 'undefined') { + return 'light'; + } + + const stored = window.localStorage.getItem(STORAGE_KEY); + if (stored === 'light' || stored === 'dark') { + return stored; + } + + const prefersDark = window.matchMedia?.( + '(prefers-color-scheme: dark)' + ).matches; + + return prefersDark ? 'dark' : 'light'; +} + +// Manages the light/dark theme, syncing it to the element's +// `data-theme` attribute (so CSS can react to it) and to localStorage +// (so the choice persists across visits). +function useTheme() { + const [theme, setTheme] = useState(getInitialTheme); + + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme); + window.localStorage.setItem(STORAGE_KEY, theme); + }, [theme]); + + function toggleTheme() { + setTheme((currentTheme) => (currentTheme === 'dark' ? 'light' : 'dark')); + } + + return [theme, toggleTheme]; +} + +export default useTheme; diff --git a/src/styles.css b/src/styles.css index f54c1cf..0385ad8 100644 --- a/src/styles.css +++ b/src/styles.css @@ -15,6 +15,30 @@ html { --color-gray-500: hsl(0deg 0% 50%); --color-gray-700: hsl(0deg 0% 75%); --color-gray-900: hsl(0deg 0% 90%); + + --color-background: hsl(0deg 0% 100%); + --color-text: var(--color-gray-100); +} + +/* + Dark theme overrides. + + The theme is toggled by setting `data-theme="dark"` on the + element (see src/hooks/useTheme.js). Colors are chosen to keep the + same relative contrast as the light theme. +*/ +html[data-theme='dark'] { + --color-success: hsl(150deg 60% 45%); + --color-warning: hsl(50deg 90% 50%); + --color-error: hsl(0deg 80% 60%); + --color-gray-100: hsl(0deg 0% 95%); + --color-gray-300: hsl(0deg 0% 75%); + --color-gray-500: hsl(0deg 0% 50%); + --color-gray-700: hsl(0deg 0% 30%); + --color-gray-900: hsl(0deg 0% 15%); + + --color-background: hsl(220deg 15% 12%); + --color-text: var(--color-gray-100); } /* @@ -22,6 +46,9 @@ html { */ body { font-family: sans-serif; + background: var(--color-background); + color: var(--color-text); + transition: background-color 200ms ease, color 200ms ease; } @media (max-height: 600px) { @@ -59,6 +86,24 @@ header .side { place-content: center; } +.theme-toggle-btn { + display: grid; + place-content: center; + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + color: inherit; + transition: background-color 150ms ease, transform 150ms ease; +} + +.theme-toggle-btn:hover { + background: var(--color-gray-900); +} + +.theme-toggle-btn:active { + transform: scale(0.92); +} + h1 { flex: 1; font-size: 2rem; @@ -155,6 +200,8 @@ h1 { display: block; width: 100%; font-size: 2rem; + background: var(--color-background); + color: var(--color-text); border: 2px solid var(--color-gray-300); border-radius: 4px; padding: 8px 16px; From 0da0c7d1ac9a1ab9301f8242dcb2539010d4c9a4 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 6 Aug 2026 18:42:45 +0000 Subject: [PATCH 2/3] Add colorblind mode toggle - Add useColorblindMode hook that tracks an on/off colorblind-friendly palette setting, persists it to localStorage, and sets a data-colorblind attribute on . - Add a second toggle button (eye icon) in the Header, next to the theme toggle, to switch the palette on/off. - Add CSS overrides for html[data-colorblind='true'] that swap the green/yellow success & warning colors for an orange/blue palette (Okabe-Ito colorblind-safe palette), with a variant tuned for when dark mode and colorblind mode are both enabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/components/App/App.js | 9 ++++++++- src/components/Header/Header.js | 35 ++++++++++++++++++++++++++++++-- src/hooks/useColorblindMode.js | 36 +++++++++++++++++++++++++++++++++ src/styles.css | 28 +++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/hooks/useColorblindMode.js diff --git a/src/components/App/App.js b/src/components/App/App.js index 5b59212..065cd79 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.js @@ -1,13 +1,20 @@ import Game from '../Game'; import Header from '../Header'; import useTheme from '../../hooks/useTheme'; +import useColorblindMode from '../../hooks/useColorblindMode'; function App() { const [theme, toggleTheme] = useTheme(); + const [colorblindMode, toggleColorblindMode] = useColorblindMode(); return (
-
+
diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index 03c840c..0e216c8 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -1,13 +1,44 @@ import React from 'react'; -function Header({ theme, onToggleTheme }) { +function Header({ + theme, + onToggleTheme, + colorblindMode, + onToggleColorblindMode, +}) { const isDark = theme === 'dark'; return (

Word Game

-
+
+