fix(theme): align initial theme and visual tokens

This commit is contained in:
2026-09-06 21:58:55 +03:30
parent 463e72a404
commit 05c304adf4
4 changed files with 44 additions and 28 deletions

View File

@@ -15,13 +15,24 @@ export const metadata: Metadata = {
], ],
}; };
const themeScript = `
try {
const savedTheme = localStorage.getItem('portfolio-theme');
const systemTheme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
document.documentElement.dataset.theme = savedTheme || systemTheme;
} catch {}
`;
export default function RootLayout({ export default function RootLayout({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
return ( return (
<html lang="en" data-theme="light"> <html lang="en" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
</head>
<body> <body>
<ThemeProvider>{children}</ThemeProvider> <ThemeProvider>{children}</ThemeProvider>
</body> </body>

View File

@@ -18,14 +18,15 @@ const ThemeContext = createContext<ThemeContextType>({
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setThemeState] = useState<Theme>('light'); const [theme, setThemeState] = useState<Theme>('light');
const [mounted, setMounted] = useState(false);
useEffect(() => { useEffect(() => {
const savedTheme = localStorage.getItem('portfolio-theme') as Theme | null; const savedTheme = localStorage.getItem('portfolio-theme') as Theme | null;
const initialTheme = savedTheme || 'light'; const systemTheme: Theme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
const initialTheme = savedTheme || systemTheme;
setThemeState(initialTheme); setThemeState(initialTheme);
document.documentElement.setAttribute('data-theme', initialTheme); document.documentElement.setAttribute('data-theme', initialTheme);
setMounted(true);
}, []); }, []);
const setTheme = (newTheme: Theme) => { const setTheme = (newTheme: Theme) => {

View File

@@ -21,18 +21,6 @@ html {
color-scheme: light; color-scheme: light;
} }
@media (max-width: 1024px) {
html {
font-size: 93.75%;
}
}
@media (max-width: 650px) {
html {
font-size: 87.5%;
}
}
html[data-theme="dark"] { html[data-theme="dark"] {
color-scheme: dark; color-scheme: dark;
} }
@@ -117,9 +105,11 @@ body {
} }
.section-title { .section-title {
font-size: var(--font-size-h2); max-width: 24ch;
line-height: var(--line-height-h2); font-size: clamp(1.875rem, 3vw, 2.5rem);
font-weight: 700; line-height: 1.15;
font-weight: 760;
letter-spacing: -0.035em;
color: var(--text-primary); color: var(--text-primary);
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
@@ -158,6 +148,20 @@ body {
box-shadow var(--motion-duration-default) ease, box-shadow var(--motion-duration-default) ease,
transform var(--motion-duration-fast) var(--motion-ease-out); transform var(--motion-duration-fast) var(--motion-ease-out);
border: 1px solid transparent; border: 1px solid transparent;
white-space: nowrap;
}
.pill-btn:focus-visible,
a:focus-visible,
button:focus-visible,
input:focus-visible {
outline: 3px solid var(--focus-ring);
outline-offset: 3px;
}
.pill-btn:active,
button:active:not(:disabled) {
transform: translateY(1px) scale(0.99);
} }
.pill-btn-outline { .pill-btn-outline {

View File

@@ -11,7 +11,7 @@
--color-blue-800: #091C3F; --color-blue-800: #091C3F;
--color-blue-900: #07132F; --color-blue-900: #07132F;
--color-blue-950: #010D27; --color-blue-950: #010D27;
--color-blue-white: #FFFFFF; --color-blue-white: #F8FAFF;
--color-slate-300: #C3CFE0; --color-slate-300: #C3CFE0;
--color-slate-400: #A3B1C6; --color-slate-400: #A3B1C6;
--color-slate-600: #667085; --color-slate-600: #667085;
@@ -20,7 +20,7 @@
--color-border-dark: #1E3458; --color-border-dark: #1E3458;
--color-border-strong-light: #A9BEDA; --color-border-strong-light: #A9BEDA;
--color-border-strong-dark: #426B9D; --color-border-strong-dark: #426B9D;
--color-black: #000000; --color-black: #07132F;
--space-0: 0; --space-0: 0;
--space-1: 0.25rem; --space-1: 0.25rem;
--space-2: 0.5rem; --space-2: 0.5rem;
@@ -67,7 +67,7 @@
--font-size-h3: 1.25rem; --font-size-h3: 1.25rem;
--font-size-body-large: 1.125rem; --font-size-body-large: 1.125rem;
--font-size-body-default: 1rem; --font-size-body-default: 1rem;
--font-size-body-small: 0.875rem; --font-size-body-small: 0.9375rem;
--font-size-label-small: 0.75rem; --font-size-label-small: 0.75rem;
--font-size-button: 0.9375rem; --font-size-button: 0.9375rem;
--line-height-display-hero: 4.5rem; --line-height-display-hero: 4.5rem;
@@ -77,7 +77,7 @@
--line-height-h3: 1.75rem; --line-height-h3: 1.75rem;
--line-height-body-large: 1.875rem; --line-height-body-large: 1.875rem;
--line-height-body-default: 1.625rem; --line-height-body-default: 1.625rem;
--line-height-body-small: 1.375rem; --line-height-body-small: 1.5rem;
--line-height-label-small: 1.125rem; --line-height-label-small: 1.125rem;
--line-height-button: 1.25rem; --line-height-button: 1.25rem;
--letter-spacing-eyebrow: 0.16em; --letter-spacing-eyebrow: 0.16em;
@@ -87,9 +87,9 @@
--motion-ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); --motion-ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
/* Semantic Tokens (Light) */ /* Semantic Tokens (Light) */
--bg-page: #FFFFFF; --bg-page: #F8FAFF;
--bg-surface: #FAFAFA; --bg-surface: #F1F6FD;
--bg-raised: #FFFFFF; --bg-raised: #FCFDFF;
--bg-subtle: #E6F0FE; --bg-subtle: #E6F0FE;
--text-primary: #0B2F5F; --text-primary: #0B2F5F;
--text-secondary: #38526E; --text-secondary: #38526E;
@@ -100,7 +100,7 @@
--accent-default: #3D8AFA; --accent-default: #3D8AFA;
--action-link: #1557A6; --action-link: #1557A6;
--action-primary-bg: #103F7F; --action-primary-bg: #103F7F;
--action-primary-fg: #FFFFFF; --action-primary-fg: #F8FAFF;
--action-primary-hover: #0B3268; --action-primary-hover: #0B3268;
--action-primary-pressed: #08284F; --action-primary-pressed: #08284F;
--brand-band: #103F7F; --brand-band: #103F7F;
@@ -131,7 +131,7 @@
--text-primary: #F8FAFF; --text-primary: #F8FAFF;
--text-secondary: #C3CFE0; --text-secondary: #C3CFE0;
--text-muted: #A3B1C6; --text-muted: #A3B1C6;
--text-on-band: #FFFFFF; --text-on-band: #F8FAFF;
--border-default: #1E3458; --border-default: #1E3458;
--border-strong: #426B9D; --border-strong: #426B9D;
--accent-default: #82B5FF; --accent-default: #82B5FF;