From 486732f23e743433afc725070b3a1c53233e40f8 Mon Sep 17 00:00:00 2001
From: "bottlenote-app[bot]"
<289617182+bottlenote-app[bot]@users.noreply.github.com>
Date: Tue, 28 Jul 2026 12:25:05 +0900
Subject: [PATCH] =?UTF-8?q?fix:=20=EC=A0=84=EC=97=AD=20=EB=8B=A4=ED=81=AC?=
=?UTF-8?q?=EB=AA=A8=EB=93=9C=20=EC=A0=81=EC=9A=A9=20=EB=B2=94=EC=9C=84=20?=
=?UTF-8?q?=EB=B3=B4=EC=99=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
DESIGN.md | 18 +--
.../whiskey-tarot/_components/IntroScreen.tsx | 2 +-
.../_components/QuestioningScreen.tsx | 2 +-
.../_components/ResultSlides.tsx | 2 +-
src/app/(primary)/layout.tsx | 2 +-
.../SettingsAppearanceSection.test.tsx | 25 ++--
.../_components/SettingsAppearanceSection.tsx | 117 ++++++++++++++---
src/app/layout.tsx | 30 ++++-
src/components/ui/Form/Toggle.tsx | 2 +-
src/components/ui/Modal/BottomSheet.tsx | 12 +-
src/lib/theme/ThemeProvider.test.tsx | 118 ++++++++++--------
src/lib/theme/ThemeProvider.tsx | 89 ++++++++-----
src/style/globals.css | 40 +++++-
tailwind.config.ts | 15 ++-
14 files changed, 341 insertions(+), 133 deletions(-)
diff --git a/DESIGN.md b/DESIGN.md
index 24ffb79e..f0702a6e 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -14,7 +14,7 @@
### 2.1 Color
-`tailwind.config.ts`의 `tailwindColors`를 우선 사용한다.
+`tailwind.config.ts`의 semantic token을 우선 사용한다. `tailwindColors` export는 기존 문서·테스트 호환을 위한 raw light palette이며, 기존 utility class는 semantic CSS 변수로 해석된다.
| Token | Hex | 주요 용도 |
| -------------- | --------- | ------------------------------------------------- |
@@ -29,9 +29,9 @@
| `mainDarkGray` | `#252525` | input text, modal sub text 등 |
| `sectionWhite` | `#F7F7F7` | 섹션/카드 배경 |
-CSS variable 기반 shadcn 계열 토큰도 존재한다: `background`, `foreground`, `card`, `primary`, `secondary`, `muted`, `accent`, `destructive`, `border`, `input`, `ring`. 단, BottleNote 고유 UI는 위 `tailwindColors`를 우선한다.
+CSS variable 기반 shadcn 계열 토큰도 존재한다: `background`, `foreground`, `card`, `primary`, `secondary`, `muted`, `accent`, `destructive`, `border`, `input`, `ring`. BottleNote 고유 UI는 `bn-*` semantic token을 우선한다.
-Semantic color 매핑은 현재 코드 사용례를 기준으로 아래처럼 해석한다. 실제 class는 우선 기존 Tailwind token 이름을 그대로 사용하고, 새 semantic alias를 코드에 추가하기 전까지 문서 기준으로만 활용한다.
+Semantic color 매핑은 현재 코드 사용례를 기준으로 아래처럼 해석한다. 새 코드는 역할이 드러나는 `bn-*` class를 사용하고, 기존 utility class는 점진적 migration을 위해 semantic CSS 변수로 매핑한다.
| Semantic | Token | Hex | 기준 |
| ----------------- | ------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------- |
@@ -46,9 +46,9 @@ Semantic color 매핑은 현재 코드 사용례를 기준으로 아래처럼
| `border-default` | `brightGray` | `#BFBFBF` | 일반 구분선/비활성 border |
| `border-brand` | `subCoral` 또는 `mainCoral` | `#E58257` / `#EF9A6E` | active/brand border는 `subCoral`, input 강조 border는 `mainCoral` 우선 |
-### 2.1.1 Dark semantic tokens (Home preview)
+### 2.1.1 Dark semantic tokens
-홈 화면 다크 프리뷰는 `dark` scope 안에서 아래 semantic alias를 사용한다. 기존 `tailwindColors`는 라이트 화면의 raw palette로 보존하며, 다크 화면에 직접 재사용하지 않는다.
+모든 프로덕트 route는 `dark` scope 안에서 아래 semantic alias를 사용한다. 기존 `tailwindColors`는 라이트 화면의 raw palette로 보존하며, 다크 화면에 직접 재사용하지 않는다.
| Semantic alias | Light | Dark: Oak & Amber Night | 역할 |
| ------------------------------- | --------------------- | ----------------------- | ---------------------------------- |
@@ -65,10 +65,10 @@ Semantic color 매핑은 현재 코드 사용례를 기준으로 아래처럼
- Tailwind class: `bg-bn-canvas`, `bg-bn-raised`, `text-bn-text`, `border-bn-border`, `border-bn-border-strong`, `bg-bn-accent-interactive`, `text-bn-accent-rating`.
- `bn-brand`, `bn-brand-tonal`, `bn-brand-foreground`은 기존 화면의 점진적 migration을 위한 compatibility alias다. 새 코드는 역할이 드러나는 `bn-accent-*` token을 사용한다.
-- 다크 모드는 `html`이 아니라 `ThemeProvider`가 만드는 app shell의 `.dark` scope에 적용한다.
-- 저장된 사용자 선택이 없으면 `prefers-color-scheme`의 시스템 테마를 초기값으로 사용한다.
-- 설정 화면의 `화면 설정 > 다크 모드` 토글 값은 `bottle-note-theme` local storage key에 보존하며, 저장값이 있으면 시스템 테마보다 우선한다.
-- 이 PR의 완전한 dark migration 대상은 홈과 설정 화면이다. 다른 route는 semantic token 전환 전까지 기존 light appearance가 일부 남을 수 있다.
+- `src/app/layout.tsx`의 pre-hydration script가 ``에 `.dark`와 `color-scheme`을 적용한다. ``은 hydration warning을 억제해야 한다.
+- `ThemeProvider`는 `system | light | dark` preference를 보유한다. 시스템 선택일 때만 `prefers-color-scheme` 변경을 구독하고, resolved theme 변경 시 `` class와 `color-scheme`을 갱신한다.
+- 설정 화면의 `화면 설정 > 화면 모드`는 `시스템 설정 따르기`, `라이트 모드`, `다크 모드` 중 하나를 선택한다. preference는 `bottle-note-theme` local storage key에 보존한다.
+- 새 페이지/공통 surface는 `bg-bn-canvas`, `bg-bn-section`, `bg-bn-raised`와 `text-bn-*`, `border-bn-*`를 사용한다. 기존 `bg-white` 등 legacy class는 다크 scope에서 compatibility rule로 보정하되, 타로 등 의도적으로 흰색이 필요한 art-directed surface에는 `keep-light-surface`를 명시한다.
### 2.2 Typography
diff --git a/src/app/(custom)/whiskey-tarot/_components/IntroScreen.tsx b/src/app/(custom)/whiskey-tarot/_components/IntroScreen.tsx
index 894fa82e..83a707f3 100644
--- a/src/app/(custom)/whiskey-tarot/_components/IntroScreen.tsx
+++ b/src/app/(custom)/whiskey-tarot/_components/IntroScreen.tsx
@@ -55,7 +55,7 @@ export default function IntroScreen({ onStart }: IntroScreenProps) {
{Array.from({ length: 50 }).map((_, i) => (
(
diff --git a/src/app/(primary)/layout.tsx b/src/app/(primary)/layout.tsx
index c38e0664..839a1717 100644
--- a/src/app/(primary)/layout.tsx
+++ b/src/app/(primary)/layout.tsx
@@ -18,7 +18,7 @@ export default function Layout({
}, [isMobile]);
return (
-
+
{children}
{loginState.isShowLoginModal && (
({
+ __esModule: true,
+ default: ({ isOpen, children }: { isOpen: boolean; children: ReactNode }) =>
+ isOpen ? {children}
: null,
+}));
+
describe('SettingsAppearanceSection', () => {
beforeEach(() => {
window.localStorage.clear();
});
- it('설정 화면에서 다크 모드를 전환한다', () => {
- const { container } = render(
+ it('화면 모드 시트에서 다크 모드를 선택하고 선택값을 저장한다', async () => {
+ render(
,
);
- fireEvent.click(screen.getByRole('button', { name: '다크 모드 꺼짐' }));
+ fireEvent.click(screen.getByRole('button', { name: /^화면 모드/ }));
+ fireEvent.click(screen.getByRole('radio', { name: /다크 모드/ }));
- expect(
- screen.getByRole('button', { name: '다크 모드 켜짐' }),
- ).toBeInTheDocument();
- expect(container.querySelector('[data-theme="dark"]')).toBeInTheDocument();
+ await waitFor(() => {
+ expect(window.localStorage.getItem('bottle-note-theme')).toBe('dark');
+ expect(document.documentElement).toHaveClass('dark');
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
+ });
});
});
diff --git a/src/app/(primary)/settings/_components/SettingsAppearanceSection.tsx b/src/app/(primary)/settings/_components/SettingsAppearanceSection.tsx
index 8144ad28..2e6a2731 100644
--- a/src/app/(primary)/settings/_components/SettingsAppearanceSection.tsx
+++ b/src/app/(primary)/settings/_components/SettingsAppearanceSection.tsx
@@ -1,28 +1,109 @@
'use client';
-import Toggle from '@/components/ui/Form/Toggle';
-import { useTheme } from '@/lib/theme/ThemeProvider';
+import { useState } from 'react';
+import BottomSheet from '@/components/ui/Modal/BottomSheet';
+import { ThemePreference, useTheme } from '@/lib/theme/ThemeProvider';
+
+const THEME_OPTIONS: Array<{
+ value: ThemePreference;
+ label: string;
+ description: string;
+}> = [
+ {
+ value: 'system',
+ label: '시스템 설정 따르기',
+ description: '기기의 화면 모드를 따릅니다.',
+ },
+ {
+ value: 'light',
+ label: '라이트 모드',
+ description: '밝은 화면으로 표시합니다.',
+ },
+ {
+ value: 'dark',
+ label: '다크 모드',
+ description: '어두운 화면으로 표시합니다.',
+ },
+];
export const SettingsAppearanceSection = () => {
- const { isDarkMode, toggleTheme } = useTheme();
+ const [isSheetOpen, setIsSheetOpen] = useState(false);
+ const { preference, theme, setThemePreference } = useTheme();
+ const selectedOption = THEME_OPTIONS.find(
+ (option) => option.value === preference,
+ );
+
+ const handleSelect = (nextPreference: ThemePreference) => {
+ setThemePreference(nextPreference);
+ setIsSheetOpen(false);
+ };
return (
-
- 화면 설정
-
-
- 다크 모드
-
-
+ 화면 설정
+ setIsSheetOpen(true)}
+ aria-haspopup="dialog"
+ aria-expanded={isSheetOpen}
+ className="mt-[27px] flex w-full items-center justify-between text-left text-15 font-medium text-mainBlack"
+ >
+ 화면 모드
+ {selectedOption?.label}
+
+
+ setIsSheetOpen(false)}
+ title="화면 모드"
+ description="화면 모드를 선택할 수 있습니다."
+ height={66}
+ className="bg-bn-raised text-bn-text"
+ >
+
+
화면 모드
+
+ {THEME_OPTIONS.map((option) => {
+ const isSelected = option.value === preference;
+
+ return (
+ handleSelect(option.value)}
+ className="flex min-h-14 w-full items-center justify-between rounded-xl border border-bn-border px-4 text-left"
+ >
+
+
+ {option.label}
+
+
+ {option.description}
+
+
+
+
+ );
+ })}
+
+
+ 현재 적용 중: {theme === 'dark' ? '다크 모드' : '라이트 모드'}
+
+
+
);
};
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 03446b36..9d8bde4a 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -6,6 +6,27 @@ import { BASE_URL } from '@/constants/common';
const isProd = process.env.NODE_ENV === 'production';
+const themeInitializationScript = `
+ (() => {
+ try {
+ const storageKey = 'bottle-note-theme';
+ const preference = localStorage.getItem(storageKey);
+ const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
+ ? 'dark'
+ : 'light';
+ const theme = preference === 'light' || preference === 'dark'
+ ? preference
+ : systemTheme;
+ const root = document.documentElement;
+
+ root.classList.toggle('dark', theme === 'dark');
+ root.style.colorScheme = theme;
+ } catch {
+ // Storage access can fail in restricted embedded browser contexts.
+ }
+ })();
+`;
+
export const metadata: Metadata = {
metadataBase: new URL(
isProd ? BASE_URL : 'https://development.bottle-note.com',
@@ -103,8 +124,11 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
+
+
-
+
-
+
{process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID && (
diff --git a/src/components/ui/Modal/BottomSheet.tsx b/src/components/ui/Modal/BottomSheet.tsx
index ae3fd90d..be262db9 100644
--- a/src/components/ui/Modal/BottomSheet.tsx
+++ b/src/components/ui/Modal/BottomSheet.tsx
@@ -8,6 +8,8 @@ interface BottomSheetProps {
children: React.ReactNode;
height?: number; // vh 단위 (기본값: 80)
className?: string;
+ title?: string;
+ description?: string;
}
export default function BottomSheet({
@@ -16,6 +18,8 @@ export default function BottomSheet({
children,
height = 80,
className = '',
+ title = '바텀 시트',
+ description = '선택 가능한 옵션 목록입니다.',
}: BottomSheetProps) {
return (
-
+ {title}
+
+ {description}
+
+
{children}
diff --git a/src/lib/theme/ThemeProvider.test.tsx b/src/lib/theme/ThemeProvider.test.tsx
index ae721947..0e70b34a 100644
--- a/src/lib/theme/ThemeProvider.test.tsx
+++ b/src/lib/theme/ThemeProvider.test.tsx
@@ -1,27 +1,50 @@
-import { fireEvent, render, screen, waitFor } from '@testing-library/react';
-import { ThemeProvider, useTheme } from './ThemeProvider';
+import {
+ act,
+ fireEvent,
+ render,
+ screen,
+ waitFor,
+} from '@testing-library/react';
+import {
+ DARK_MODE_MEDIA_QUERY,
+ THEME_STORAGE_KEY,
+ ThemeProvider,
+ useTheme,
+} from './ThemeProvider';
+
+let systemThemeListener: ((event: MediaQueryListEvent) => void) | undefined;
const mockSystemTheme = (isDarkMode: boolean) => {
window.matchMedia = jest.fn().mockImplementation((query: string) => ({
- matches: isDarkMode && query === '(prefers-color-scheme: dark)',
+ matches: isDarkMode && query === DARK_MODE_MEDIA_QUERY,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
- addEventListener: jest.fn(),
+ addEventListener: jest.fn(
+ (_eventName: string, listener: (event: MediaQueryListEvent) => void) => {
+ systemThemeListener = listener;
+ },
+ ),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
}));
};
const ThemeConsumer = () => {
- const { theme, toggleTheme } = useTheme();
+ const { preference, theme, setThemePreference } = useTheme();
return (
<>
- {theme}
-
- 테마 전환
+ {`${preference}:${theme}`}
+ setThemePreference('light')}>
+ 라이트 모드
+
+ setThemePreference('dark')}>
+ 다크 모드
+
+ setThemePreference('system')}>
+ 시스템 설정 따르기
>
);
@@ -30,91 +53,76 @@ const ThemeConsumer = () => {
describe('ThemeProvider', () => {
beforeEach(() => {
window.localStorage.clear();
+ document.documentElement.className = 'touch-manipulation';
+ document.documentElement.style.colorScheme = '';
+ systemThemeListener = undefined;
mockSystemTheme(false);
});
- it('저장된 테마가 없으면 시스템 라이트 모드를 따른다', async () => {
- const { container } = render(
-
-
- ,
- );
-
- await waitFor(() => {
- expect(window.matchMedia).toHaveBeenCalledWith(
- '(prefers-color-scheme: dark)',
- );
- expect(screen.getByText('light')).toBeInTheDocument();
- expect(
- container.querySelector('[data-theme="light"]'),
- ).toBeInTheDocument();
- });
- });
-
- it('저장된 테마가 없으면 시스템 다크 모드를 따른다', async () => {
+ it('저장값이 없으면 시스템 테마를 적용한다', async () => {
mockSystemTheme(true);
- const { container } = render(
+ render(
,
);
await waitFor(() => {
- expect(screen.getByText('dark')).toBeInTheDocument();
- expect(
- container.querySelector('[data-theme="dark"]'),
- ).toBeInTheDocument();
+ expect(screen.getByText('system:dark')).toBeInTheDocument();
+ expect(document.documentElement).toHaveClass('dark');
+ expect(document.documentElement.style.colorScheme).toBe('dark');
});
});
- it('저장된 다크 모드를 복원한다', async () => {
- window.localStorage.setItem('bottle-note-theme', 'dark');
+ it('저장된 선택값은 시스템 테마보다 우선한다', async () => {
+ mockSystemTheme(true);
+ window.localStorage.setItem(THEME_STORAGE_KEY, 'light');
- const { container } = render(
+ render(
,
);
await waitFor(() => {
- expect(screen.getByText('dark')).toBeInTheDocument();
- expect(
- container.querySelector('[data-theme="dark"]'),
- ).toBeInTheDocument();
+ expect(screen.getByText('light:light')).toBeInTheDocument();
+ expect(document.documentElement).not.toHaveClass('dark');
+ expect(document.documentElement.style.colorScheme).toBe('light');
});
});
- it('저장된 라이트 모드는 시스템 다크 모드보다 우선한다', async () => {
- mockSystemTheme(true);
- window.localStorage.setItem('bottle-note-theme', 'light');
-
- const { container } = render(
+ it('명시 선택을 저장하고 html 테마를 즉시 갱신한다', async () => {
+ render(
,
);
+ fireEvent.click(screen.getByRole('button', { name: '다크 모드' }));
+
await waitFor(() => {
- expect(screen.getByText('light')).toBeInTheDocument();
- expect(
- container.querySelector('[data-theme="light"]'),
- ).toBeInTheDocument();
+ expect(screen.getByText('dark:dark')).toBeInTheDocument();
+ expect(window.localStorage.getItem(THEME_STORAGE_KEY)).toBe('dark');
+ expect(document.documentElement).toHaveClass('dark');
+ expect(document.documentElement.style.colorScheme).toBe('dark');
});
- expect(window.matchMedia).not.toHaveBeenCalled();
});
- it('테마를 전환하고 선택값을 저장한다', () => {
- const { container } = render(
+ it('시스템 설정 추종 중에는 OS 테마 변경을 반영한다', async () => {
+ render(
,
);
- fireEvent.click(screen.getByRole('button', { name: '테마 전환' }));
+ await waitFor(() => expect(systemThemeListener).toBeDefined());
+
+ act(() => {
+ systemThemeListener?.({ matches: true } as MediaQueryListEvent);
+ });
- expect(screen.getByText('dark')).toBeInTheDocument();
- expect(container.querySelector('.dark')).toBeInTheDocument();
- expect(window.localStorage.getItem('bottle-note-theme')).toBe('dark');
+ expect(screen.getByText('system:dark')).toBeInTheDocument();
+ expect(document.documentElement).toHaveClass('dark');
});
});
diff --git a/src/lib/theme/ThemeProvider.tsx b/src/lib/theme/ThemeProvider.tsx
index 2086ba21..9dbadc4d 100644
--- a/src/lib/theme/ThemeProvider.tsx
+++ b/src/lib/theme/ThemeProvider.tsx
@@ -11,69 +11,96 @@ import {
} from 'react';
export type Theme = 'light' | 'dark';
+export type ThemePreference = Theme | 'system';
interface ThemeContextValue {
+ preference: ThemePreference;
theme: Theme;
isDarkMode: boolean;
- setTheme: (theme: Theme) => void;
- toggleTheme: () => void;
+ setThemePreference: (preference: ThemePreference) => void;
}
interface ThemeProviderProps {
children: ReactNode;
}
-const THEME_STORAGE_KEY = 'bottle-note-theme';
-const DARK_MODE_MEDIA_QUERY = '(prefers-color-scheme: dark)';
+export const THEME_STORAGE_KEY = 'bottle-note-theme';
+export const DARK_MODE_MEDIA_QUERY = '(prefers-color-scheme: dark)';
const ThemeContext = createContext(null);
-const getSystemTheme = (): Theme =>
- window.matchMedia?.(DARK_MODE_MEDIA_QUERY).matches ? 'dark' : 'light';
+const isThemePreference = (value: string | null): value is ThemePreference =>
+ value === 'light' || value === 'dark' || value === 'system';
+
+const readThemePreference = (): ThemePreference => {
+ try {
+ const storedPreference = window.localStorage.getItem(THEME_STORAGE_KEY);
+ return isThemePreference(storedPreference) ? storedPreference : 'system';
+ } catch {
+ return 'system';
+ }
+};
+
+const applyTheme = (theme: Theme) => {
+ const root = document.documentElement;
+ root.classList.toggle('dark', theme === 'dark');
+ root.style.colorScheme = theme;
+};
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
- const [theme, setThemeState] = useState('light');
+ const [preference, setPreference] = useState('system');
+ const [systemTheme, setSystemTheme] = useState('light');
+ const [isReady, setIsReady] = useState(false);
useEffect(() => {
- const storedTheme = window.localStorage.getItem(THEME_STORAGE_KEY);
+ setPreference(readThemePreference());
+ setIsReady(true);
+ }, []);
- if (storedTheme === 'light' || storedTheme === 'dark') {
- setThemeState(storedTheme);
- return;
+ useEffect(() => {
+ if (!isReady || preference !== 'system' || !window.matchMedia) {
+ return undefined;
}
- setThemeState(getSystemTheme());
- }, []);
+ const mediaQuery = window.matchMedia(DARK_MODE_MEDIA_QUERY);
+ const syncSystemTheme = (event?: MediaQueryListEvent) => {
+ setSystemTheme(event?.matches ?? mediaQuery.matches ? 'dark' : 'light');
+ };
- const setTheme = useCallback((nextTheme: Theme) => {
- setThemeState(nextTheme);
- window.localStorage.setItem(THEME_STORAGE_KEY, nextTheme);
- }, []);
+ syncSystemTheme();
+ mediaQuery.addEventListener('change', syncSystemTheme);
+
+ return () => mediaQuery.removeEventListener('change', syncSystemTheme);
+ }, [isReady, preference]);
+
+ const theme = preference === 'system' ? systemTheme : preference;
+
+ useEffect(() => {
+ if (isReady) applyTheme(theme);
+ }, [isReady, theme]);
+
+ const setThemePreference = useCallback((nextPreference: ThemePreference) => {
+ try {
+ window.localStorage.setItem(THEME_STORAGE_KEY, nextPreference);
+ } catch {
+ // Storage can be disabled in private or embedded browser contexts.
+ }
- const toggleTheme = useCallback(() => {
- setThemeState((currentTheme) => {
- const nextTheme = currentTheme === 'dark' ? 'light' : 'dark';
- window.localStorage.setItem(THEME_STORAGE_KEY, nextTheme);
- return nextTheme;
- });
+ setPreference(nextPreference);
}, []);
const value = useMemo(
() => ({
+ preference,
theme,
isDarkMode: theme === 'dark',
- setTheme,
- toggleTheme,
+ setThemePreference,
}),
- [setTheme, theme, toggleTheme],
+ [preference, setThemePreference, theme],
);
return (
-
-
- {children}
-
-
+ {children}
);
};
diff --git a/src/style/globals.css b/src/style/globals.css
index e6597cd7..1466fabc 100644
--- a/src/style/globals.css
+++ b/src/style/globals.css
@@ -340,6 +340,44 @@
}
@layer utilities {
+ /* Legacy light surface utilities used across existing product routes. */
+ .dark .bg-white {
+ background-color: hsl(var(--bn-raised) / var(--tw-bg-opacity, 1));
+ }
+
+ .dark .keep-light-surface {
+ background-color: hsl(0 0% 100% / var(--tw-bg-opacity, 1));
+ }
+
+ .dark main.bg-white,
+ .dark .content-container.bg-white,
+ .dark .min-h-safe-screen.bg-white {
+ background-color: hsl(var(--bn-canvas) / var(--tw-bg-opacity, 1));
+ }
+
+ .dark .bg-gray-50,
+ .dark .bg-gray-100,
+ .dark .bg-gray-200 {
+ background-color: hsl(var(--bn-section) / var(--tw-bg-opacity, 1));
+ }
+
+ .dark .text-black,
+ .dark .text-gray-900 {
+ color: hsl(var(--bn-text) / var(--tw-text-opacity, 1));
+ }
+
+ .dark .border-gray-100,
+ .dark .border-gray-200,
+ .dark .border-gray-300 {
+ border-color: hsl(var(--bn-border-subtle) / var(--tw-border-opacity, 1));
+ }
+
+ .dark .hover\:bg-gray-50:hover,
+ .dark .hover\:bg-gray-100:hover,
+ .dark .active\:bg-gray-100:active {
+ background-color: hsl(var(--bn-section) / var(--tw-bg-opacity, 1));
+ }
+
@keyframes auto-marquee-text {
0%,
10% {
@@ -399,6 +437,6 @@
@apply border-border;
}
body {
- @apply bg-background text-foreground;
+ @apply bg-bn-canvas text-bn-text;
}
}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 217bb6e2..3c167d4d 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -14,6 +14,19 @@ export const tailwindColors = {
sectionWhite: '#F7F7F7',
};
+const legacySemanticColors = {
+ mainCoral: 'hsl(var(--bn-accent-hover) / )',
+ subCoral: 'hsl(var(--bn-accent-interactive) / )',
+ bgGray: 'hsl(var(--bn-section) / )',
+ brightGray: 'hsl(var(--bn-border-strong) / )',
+ mainGray: 'hsl(var(--bn-text-secondary) / )',
+ textGray: 'hsl(var(--bn-text-tertiary) / )',
+ gray: 'hsl(var(--bn-text) / )',
+ mainBlack: 'hsl(var(--bn-text) / )',
+ mainDarkGray: 'hsl(var(--bn-text) / )',
+ sectionWhite: 'hsl(var(--bn-section) / )',
+};
+
const config: Config = {
darkMode: ['class'],
content: [
@@ -93,7 +106,7 @@ const config: Config = {
foreground: 'hsl(var(--bn-brand-foreground) / )',
},
},
- ...tailwindColors,
+ ...legacySemanticColors,
},
fontSize: {
'9': ['9px', '9px'],