Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,9 @@
--foreground: #171717;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
.dark {
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The theme variables are currently scoped to the generic .dark class selector. This will apply to any element with class dark, not just the root element. To make the intent explicit and avoid accidental scoping changes later, consider using html.dark / :root.dark instead.

Suggested change
.dark {
:root.dark {

Copilot uses AI. Check for mistakes.
--background: #0a0a0a;
--foreground: #ededed;
}
Comment on lines +8 to 11
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By removing the @media (prefers-color-scheme: dark) fallback and relying solely on the .dark class, users will no longer get automatic dark mode if JavaScript is disabled or the inline script fails. If that fallback behavior matters, consider keeping a media-query-based default (e.g., applying dark variables when prefers-color-scheme: dark and no explicit saved theme is present).

Copilot uses AI. Check for mistakes.

body {
Expand Down
25 changes: 24 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,30 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning>
<head>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
try {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark' || savedTheme === 'light') {
if (savedTheme === 'dark') {
document.documentElement.classList.add('dark');
}
return;
}
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (isDark) {
document.documentElement.classList.add('dark');
}
Comment on lines +25 to +33
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline theme bootstrap script returns early for savedTheme === 'light' without explicitly clearing a pre-existing dark class. While the server markup currently doesn’t set it, making the script idempotent via classList.toggle('dark', savedTheme === 'dark') (and only returning after applying) avoids stale state if the class is ever present for any reason.

Suggested change
if (savedTheme === 'dark') {
document.documentElement.classList.add('dark');
}
return;
}
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (isDark) {
document.documentElement.classList.add('dark');
}
document.documentElement.classList.toggle('dark', savedTheme === 'dark');
return;
}
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', isDark);

Copilot uses AI. Check for mistakes.
} catch (e) {}
})();
`,
}}
/>
</head>
<body className="antialiased">
<ThemeProvider>
{children}
Expand Down
41 changes: 25 additions & 16 deletions src/app/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,38 @@ interface ThemeContextType {

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>(() => {
if (typeof window === "undefined") {
return "light";
}
function getInitialTheme(): Theme {
if (typeof window === "undefined") {
return "light";
}

const savedTheme = window.localStorage.getItem("theme");
if (savedTheme === "light" || savedTheme === "dark") {
return savedTheme;
}

const savedTheme = window.localStorage.getItem("theme");
if (savedTheme === "light" || savedTheme === "dark") {
return savedTheme;
}
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
return isDark ? "dark" : "light";
Comment on lines +19 to +25
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getInitialTheme() reads window.localStorage and window.matchMedia without any error handling. Both can throw (e.g., storage blocked/disabled) which would surface as an uncaught error in the mount effect. Consider wrapping these reads in a try/catch and falling back to "light" (or to matchMedia only if available).

Copilot uses AI. Check for mistakes.
}

export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const [mounted, setMounted] = useState(false);

const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
return isDark ? "dark" : "light";
});
useEffect(() => {
setMounted(true);
const initialTheme = getInitialTheme();
setTheme(initialTheme);
document.documentElement.classList.toggle("dark", initialTheme === "dark");
}, []);

useEffect(() => {
if (typeof window === "undefined") return;
if (!mounted) return;
document.documentElement.classList.toggle("dark", theme === "dark");
}, [theme]);
}, [theme, mounted]);

const toggleTheme = () => {
if (typeof window === "undefined") return;

if (!mounted) return;
const newTheme = theme === "light" ? "dark" : "light";
setTheme(newTheme);
window.localStorage.setItem("theme", newTheme);
Expand Down
Loading