From 5adeb16a1e573d8c6911b2818d0b975261cf5b5f Mon Sep 17 00:00:00 2001 From: GitHub Ace Date: Thu, 16 Oct 2025 20:43:50 +0000 Subject: [PATCH 1/3] feat(theme): add dark mode and high contrast support Co-authored-by: David Dossett --- src/App.tsx | 24 +++++++++---------- src/index.css | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 77b045d..a0bdd45 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -94,36 +94,36 @@ function App() { if (loading) { return ( -
-

Loading...

+
+

Loading...

) } if (error) { return ( -
-

{error}

+
+

{error}

) } return ( -
-
+
+
-

+

Calm HN

-

+

Top stories from the last three months

{stories.map((story, index) => ( -
+
- + {index + 1} -

+

{story.title} {story.url && ( @@ -144,7 +144,7 @@ function App() {

-
+
{story.score} diff --git a/src/index.css b/src/index.css index b329b2a..81d3fe7 100644 --- a/src/index.css +++ b/src/index.css @@ -8,6 +8,72 @@ src: url("./InterVariable.woff2") format("woff2"); } +/* Light theme (default) */ +:root { + --bg-primary: 250 250 250; /* neutral-50 */ + --bg-secondary: 241 245 249; /* slate-100 */ + --bg-badge: 226 232 240; /* slate-200 */ + --bg-badge-hover: 254 215 170; /* orange-200 */ + --gradient-from: 254 215 170; /* orange-200 */ + --text-primary: 15 23 42; /* slate-900 */ + --text-secondary: 100 116 139; /* slate-500 */ + --text-tertiary: 148 163 184; /* slate-400 */ + --text-badge: 100 116 139; /* slate-500 */ + --text-badge-hover: 71 85 105; /* slate-600 */ + --text-muted: 203 213 225; /* slate-300 */ +} + +/* Dark theme */ +@media (prefers-color-scheme: dark) { + :root { + --bg-primary: 15 23 42; /* slate-900 */ + --bg-secondary: 30 41 59; /* slate-800 */ + --bg-badge: 51 65 85; /* slate-700 */ + --bg-badge-hover: 194 65 12; /* orange-700 */ + --gradient-from: 194 65 12; /* orange-700 */ + --text-primary: 248 250 252; /* slate-50 */ + --text-secondary: 203 213 225; /* slate-300 */ + --text-tertiary: 148 163 184; /* slate-400 */ + --text-badge: 203 213 225; /* slate-300 */ + --text-badge-hover: 226 232 240; /* slate-200 */ + --text-muted: 71 85 105; /* slate-600 */ + } +} + +/* High contrast light theme */ +@media (prefers-contrast: more) and (prefers-color-scheme: light) { + :root { + --bg-primary: 255 255 255; /* white */ + --bg-secondary: 226 232 240; /* slate-200 */ + --bg-badge: 203 213 225; /* slate-300 */ + --bg-badge-hover: 251 146 60; /* orange-400 */ + --gradient-from: 251 146 60; /* orange-400 */ + --text-primary: 0 0 0; /* black */ + --text-secondary: 30 41 59; /* slate-800 */ + --text-tertiary: 71 85 105; /* slate-600 */ + --text-badge: 30 41 59; /* slate-800 */ + --text-badge-hover: 0 0 0; /* black */ + --text-muted: 148 163 184; /* slate-400 */ + } +} + +/* High contrast dark theme */ +@media (prefers-contrast: more) and (prefers-color-scheme: dark) { + :root { + --bg-primary: 0 0 0; /* black */ + --bg-secondary: 30 41 59; /* slate-800 */ + --bg-badge: 71 85 105; /* slate-600 */ + --bg-badge-hover: 255 140 0; /* bright orange */ + --gradient-from: 255 140 0; /* bright orange */ + --text-primary: 255 255 255; /* white */ + --text-secondary: 226 232 240; /* slate-200 */ + --text-tertiary: 203 213 225; /* slate-300 */ + --text-badge: 241 245 249; /* slate-100 */ + --text-badge-hover: 255 255 255; /* white */ + --text-muted: 100 116 139; /* slate-500 */ + } +} + body { font-family: "InterVariable", sans-serif; } \ No newline at end of file From 7f8580fa34d95356cd38138c4d13e2701b27041d Mon Sep 17 00:00:00 2001 From: GitHub Ace Date: Thu, 16 Oct 2025 20:45:19 +0000 Subject: [PATCH 2/3] feat(a11y): add color blindness support Co-authored-by: David Dossett --- src/index.css | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.tsx | 14 +++++++++ 2 files changed, 95 insertions(+) diff --git a/src/index.css b/src/index.css index 81d3fe7..f34bc63 100644 --- a/src/index.css +++ b/src/index.css @@ -74,6 +74,87 @@ } } +/* Protanopia & Deuteranopia light theme (red-green color blindness) */ +/* Uses blue-yellow contrasts instead of red-green */ +@media (prefers-color-scheme: light) { + @supports (color: color(display-p3 1 1 1)) { + :root:has(body[data-cvd="protanopia"]), + :root:has(body[data-cvd="deuteranopia"]) { + --bg-primary: 250 250 250; /* neutral-50 */ + --bg-secondary: 241 245 249; /* slate-100 */ + --bg-badge: 219 234 254; /* blue-100 */ + --bg-badge-hover: 147 197 253; /* blue-300 */ + --gradient-from: 147 197 253; /* blue-300 */ + --text-primary: 15 23 42; /* slate-900 */ + --text-secondary: 71 85 105; /* slate-600 */ + --text-tertiary: 100 116 139; /* slate-500 */ + --text-badge: 30 64 175; /* blue-800 */ + --text-badge-hover: 30 58 138; /* blue-900 */ + --text-muted: 203 213 225; /* slate-300 */ + } + } +} + +/* Protanopia & Deuteranopia dark theme */ +@media (prefers-color-scheme: dark) { + @supports (color: color(display-p3 1 1 1)) { + :root:has(body[data-cvd="protanopia"]), + :root:has(body[data-cvd="deuteranopia"]) { + --bg-primary: 15 23 42; /* slate-900 */ + --bg-secondary: 30 41 59; /* slate-800 */ + --bg-badge: 30 58 138; /* blue-900 */ + --bg-badge-hover: 59 130 246; /* blue-500 */ + --gradient-from: 59 130 246; /* blue-500 */ + --text-primary: 248 250 252; /* slate-50 */ + --text-secondary: 203 213 225; /* slate-300 */ + --text-tertiary: 148 163 184; /* slate-400 */ + --text-badge: 147 197 253; /* blue-300 */ + --text-badge-hover: 191 219 254; /* blue-200 */ + --text-muted: 71 85 105; /* slate-600 */ + } + } +} + +/* Protanopia & Deuteranopia high contrast light */ +@media (prefers-contrast: more) and (prefers-color-scheme: light) { + @supports (color: color(display-p3 1 1 1)) { + :root:has(body[data-cvd="protanopia"]), + :root:has(body[data-cvd="deuteranopia"]) { + --bg-primary: 255 255 255; /* white */ + --bg-secondary: 226 232 240; /* slate-200 */ + --bg-badge: 191 219 254; /* blue-200 */ + --bg-badge-hover: 96 165 250; /* blue-400 */ + --gradient-from: 96 165 250; /* blue-400 */ + --text-primary: 0 0 0; /* black */ + --text-secondary: 30 41 59; /* slate-800 */ + --text-tertiary: 51 65 85; /* slate-700 */ + --text-badge: 29 78 216; /* blue-700 */ + --text-badge-hover: 0 0 0; /* black */ + --text-muted: 148 163 184; /* slate-400 */ + } + } +} + +/* Protanopia & Deuteranopia high contrast dark */ +@media (prefers-contrast: more) and (prefers-color-scheme: dark) { + @supports (color: color(display-p3 1 1 1)) { + :root:has(body[data-cvd="protanopia"]), + :root:has(body[data-cvd="deuteranopia"]) { + --bg-primary: 0 0 0; /* black */ + --bg-secondary: 30 41 59; /* slate-800 */ + --bg-badge: 29 78 216; /* blue-700 */ + --bg-badge-hover: 96 165 250; /* blue-400 */ + --gradient-from: 96 165 250; /* blue-400 */ + --text-primary: 255 255 255; /* white */ + --text-secondary: 226 232 240; /* slate-200 */ + --text-tertiary: 203 213 225; /* slate-300 */ + --text-badge: 219 234 254; /* blue-100 */ + --text-badge-hover: 255 255 255; /* white */ + --text-muted: 100 116 139; /* slate-500 */ + } + } +} + body { font-family: "InterVariable", sans-serif; } \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index bef5202..cd21b50 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,6 +3,20 @@ import { createRoot } from 'react-dom/client' import './index.css' import App from './App.tsx' +// Detect color vision deficiency preference +// Users can set this via browser extension or URL parameter +const params = new URLSearchParams(window.location.search) +const cvdParam = params.get('cvd') +if (cvdParam === 'protanopia' || cvdParam === 'deuteranopia') { + document.body.setAttribute('data-cvd', cvdParam) + localStorage.setItem('cvd-preference', cvdParam) +} else { + const stored = localStorage.getItem('cvd-preference') + if (stored === 'protanopia' || stored === 'deuteranopia') { + document.body.setAttribute('data-cvd', stored) + } +} + createRoot(document.getElementById('root')!).render( From f657ef10f5e18fc81197736e03edd2d690fccfad Mon Sep 17 00:00:00 2001 From: GitHub Ace Date: Thu, 16 Oct 2025 20:50:22 +0000 Subject: [PATCH 3/3] feat(accessibility): add color vision deficiency mode Co-authored-by: Idan Gazit --- src/App.tsx | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a0bdd45..9342bfd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,6 +25,9 @@ function App() { const [stories, setStories] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + const [cvdMode, setCvdMode] = useState( + localStorage.getItem('cvd-preference') + ) useEffect(() => { const fetchTopStories = async () => { @@ -69,6 +72,17 @@ function App() { fetchTopStories() }, []) + const handleCvdChange = (mode: string | null) => { + setCvdMode(mode) + if (mode) { + document.body.setAttribute('data-cvd', mode) + localStorage.setItem('cvd-preference', mode) + } else { + document.body.removeAttribute('data-cvd') + localStorage.removeItem('cvd-preference') + } + } + const formatTime = (timestamp: number) => { const now = Date.now() / 1000 const diff = now - timestamp @@ -112,13 +126,25 @@ function App() {
-
-

- Calm HN -

-

- Top stories from the last three months -

+
+
+

+ Calm HN +

+

+ Top stories from the last three months +

+
+