diff --git a/web/src/components/theme-switch.tsx b/web/src/components/theme-switch.tsx index badcdd2..af590c4 100644 --- a/web/src/components/theme-switch.tsx +++ b/web/src/components/theme-switch.tsx @@ -1,47 +1,275 @@ -// -// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . +import { useEffect } from 'react' +import { Moon, Sun, Check } from 'lucide-react' +import { useTranslation } from 'react-i18next' - -import { useCallback, useEffect } from 'react' -import { IconMoon, IconSun } from '@tabler/icons-react' -import { useTheme } from '@/context/theme-context' +import { Theme, useTheme } from '@/context/theme-context' import { Button } from '@/components/ui/button' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover' +import { cn } from '@/lib/utils' + +const themeColors: Record = { + 'light': '#ffffff', + 'dark': '#020817', + 'rose-light': '#fb7185', + 'rose-dark': '#881337', + 'orange-light': '#fb923c', + 'orange-dark': '#9a3412', + 'green-light': '#4ade80', + 'green-dark': '#166534', + 'yellow-light': '#facc15', + 'yellow-dark': '#a16207', + 'blue-light': '#60a5fa', + 'blue-dark': '#1e40af', +} + +const LIGHT_THEMES = [ + { + value: 'light', + labelKey: 'theme.default', + background: '#ffffff', + color: '#0f172a', + muted: '#cbd5e1', + }, + { + value: 'rose-light', + labelKey: 'theme.rose', + background: '#fff1f2', + color: '#e11d48', + muted: '#fda4af', + }, + { + value: 'orange-light', + labelKey: 'theme.orange', + background: '#fff7ed', + color: '#ea580c', + muted: '#fdba74', + }, + { + value: 'green-light', + labelKey: 'theme.green', + background: '#f0fdf4', + color: '#16a34a', + muted: '#86efac', + }, + { + value: 'yellow-light', + labelKey: 'theme.yellow', + background: '#fefce8', + color: '#ca8a04', + muted: '#fde047', + }, + { + value: 'blue-light', + labelKey: 'theme.blue', + background: '#eff6ff', + color: '#2563eb', + muted: '#bfdbfe', + }, +] as const + +const DARK_THEMES = [ + { + value: 'dark', + labelKey: 'theme.default', + background: '#020817', + color: '#94a3b8', + muted: '#334155', + }, + { + value: 'rose-dark', + labelKey: 'theme.rose', + background: '#1f1115', + color: '#e11d48', + muted: '#881337', + }, + { + value: 'orange-dark', + labelKey: 'theme.orange', + background: '#1c120d', + color: '#f97316', + muted: '#9a3412', + }, + { + value: 'green-dark', + labelKey: 'theme.green', + background: '#0d1b12', + color: '#22c55e', + muted: '#166534', + }, + { + value: 'yellow-dark', + labelKey: 'theme.yellow', + background: '#1c1608', + color: '#facc15', + muted: '#a16207', + }, + { + value: 'blue-dark', + labelKey: 'theme.blue', + background: '#172554', + color: '#60a5fa', + muted: '#1e3a8a', + } +] as const + +type ThemeOption = + | (typeof LIGHT_THEMES)[number] + | (typeof DARK_THEMES)[number] + +function ThemePreview({ + background, + primary, + muted, +}: { + background: string + primary: string + muted: string +}) { + return ( +
+
+ + + +
+
+
+
+
+ ) +} + +function ThemeItem({ + item, + active, + onSelect, +}: { + item: ThemeOption + active: boolean + onSelect: (v: Theme) => void +}) { + const { t } = useTranslation() + + return ( + + ) +} + +function ThemeColumn({ + icon, + label, + items, + currentTheme, + onSelect, +}: { + icon: React.ReactNode + label: string + items: readonly ThemeOption[] + currentTheme: Theme + onSelect: (v: Theme) => void +}) { + return ( +
+
+ {icon} + {label} +
+ + {items.map((item) => ( + + ))} +
+ ) +} export function ThemeSwitch() { const { theme, setTheme } = useTheme() + const { t } = useTranslation() + const isDark = theme.includes('dark') useEffect(() => { - const themeColor = theme === 'dark' ? '#020817' : '#fff' - const metaThemeColor = document.querySelector("meta[name='theme-color']") - if (metaThemeColor) metaThemeColor.setAttribute('content', themeColor) - }, [theme]) - - const toggleTheme = useCallback(() => { - setTheme(theme === 'dark' ? 'light' : 'dark') + document + .querySelector("meta[name='theme-color']") + ?.setAttribute('content', themeColors[theme]) }, [theme]) return ( - + + + + + + +

+ {t('theme.appearance')} +

+ +
+ } + label={t('theme.light')} + items={LIGHT_THEMES} + currentTheme={theme} + onSelect={setTheme} + /> + +
+ } + label={t('theme.dark')} + items={DARK_THEMES} + currentTheme={theme} + onSelect={setTheme} + /> +
+
+
+
) } \ No newline at end of file diff --git a/web/src/context/theme-context.tsx b/web/src/context/theme-context.tsx index f8c67b4..3d7c0d8 100644 --- a/web/src/context/theme-context.tsx +++ b/web/src/context/theme-context.tsx @@ -19,7 +19,8 @@ import { createContext, useContext, useEffect, useState } from 'react' -type Theme = 'dark' | 'light' +export type Theme = 'light' | 'dark' | 'rose-light' | 'rose-dark' | 'orange-light' + | 'orange-dark' | 'green-light' | 'green-dark' | 'yellow-light' | 'yellow-dark' | 'blue-light' | 'blue-dark' type ThemeProviderProps = { children: React.ReactNode @@ -41,7 +42,7 @@ const ThemeProviderContext = createContext(initialState) export function ThemeProvider({ children, - defaultTheme = 'dark', + defaultTheme = 'light', storageKey = 'vite-ui-theme', ...props }: ThemeProviderProps) { @@ -51,7 +52,8 @@ export function ThemeProvider({ useEffect(() => { const root = window.document.documentElement - root.classList.remove('light', 'dark') + root.classList.remove('light', 'dark', 'rose-light', 'rose-dark', 'orange-light', + 'orange-dark', 'green-light', 'green-dark', 'yellow-light', 'yellow-dark', 'blue-light', 'blue-dark') root.classList.add(theme) localStorage.setItem(storageKey, theme) }, [theme, storageKey]) diff --git a/web/src/features/settings/appearance/appearance-form.tsx b/web/src/features/settings/appearance/appearance-form.tsx index a1ac050..a0c2561 100644 --- a/web/src/features/settings/appearance/appearance-form.tsx +++ b/web/src/features/settings/appearance/appearance-form.tsx @@ -1,22 +1,3 @@ -// -// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - - import { z } from 'zod' import { useForm } from 'react-hook-form' import { CaretSortIcon, CheckIcon } from '@radix-ui/react-icons' @@ -32,8 +13,8 @@ import { FormLabel, FormMessage, } from '@/components/ui/form' -import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' -import { useTheme } from '@/context/theme-context' +import { RadioGroup } from '@/components/ui/radio-group' +import { useTheme, Theme } from '@/context/theme-context' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command' import { useTranslation } from 'react-i18next' @@ -42,7 +23,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { update_user } from '@/api/users/api' import { toast } from '@/hooks/use-toast' import { AxiosError } from 'axios' - +import { Card } from '@/components/ui/card' const languages = [ { value: 'ar', label: 'العربية' }, @@ -65,8 +46,28 @@ const languages = [ { value: 'zh-tw', label: '繁體中文' }, ] +const LIGHT_THEMES = [ + { value: 'light', labelKey: 'theme.default', background: '#ffffff', color: '#0f172a', muted: '#cbd5e1' }, + { value: 'rose-light', labelKey: 'theme.rose', background: '#fff1f2', color: '#e11d48', muted: '#fda4af' }, + { value: 'orange-light', labelKey: 'theme.orange', background: '#fff7ed', color: '#ea580c', muted: '#fdba74' }, + { value: 'green-light', labelKey: 'theme.green', background: '#f0fdf4', color: '#16a34a', muted: '#86efac' }, + { value: 'yellow-light', labelKey: 'theme.yellow', background: '#fefce8', color: '#ca8a04', muted: '#fde047' }, + { value: 'blue-light', labelKey: 'theme.blue', background: '#eff6ff', color: '#2563eb', muted: '#bfdbfe' }, +] as const + +const DARK_THEMES = [ + { value: 'dark', labelKey: 'theme.default', background: '#020817', color: '#94a3b8', muted: '#334155' }, + { value: 'rose-dark', labelKey: 'theme.rose', background: '#1f1115', color: '#e11d48', muted: '#881337' }, + { value: 'orange-dark', labelKey: 'theme.orange', background: '#1c120d', color: '#f97316', muted: '#9a3412' }, + { value: 'green-dark', labelKey: 'theme.green', background: '#0d1b12', color: '#22c55e', muted: '#166534' }, + { value: 'yellow-dark', labelKey: 'theme.yellow', background: '#1c1608', color: '#facc15', muted: '#a16207' }, + { value: 'blue-dark', labelKey: 'theme.blue', background: '#172554', color: '#60a5fa', muted: '#1e3a8a' } +] as const + +const ALL_THEME_VALUES = [...LIGHT_THEMES.map(t => t.value), ...DARK_THEMES.map(t => t.value)] as [string, ...string[]] + const appearanceSchema = (t: (key: string) => string) => z.object({ - theme: z.enum(['light', 'dark'], { + theme: z.enum(ALL_THEME_VALUES, { required_error: t('settings.appearance.validation.theme.required'), }), language: z.string({ @@ -76,19 +77,30 @@ const appearanceSchema = (t: (key: string) => string) => z.object({ type AppearanceFormValues = z.infer> +function ThemePreview({ background, primary, muted }: { background: string; primary: string; muted: string }) { + return ( +
+
+ + +
+
+
+
+ ) +} export function AppearanceForm() { - const { data: user } = useCurrentUser(); - const queryClient = useQueryClient(); - const { t, i18n } = useTranslation(); - const { theme, setTheme } = useTheme(); - + const { data: user } = useCurrentUser() + const queryClient = useQueryClient() + const { t, i18n } = useTranslation() + const { theme, setTheme } = useTheme() const form = useForm({ resolver: zodResolver(appearanceSchema(t)), mode: 'onChange', defaultValues: { - theme: (theme as 'light' | 'dark') || 'light', + theme: (theme as any) || 'light', language: i18n.language || 'en', }, }) @@ -108,21 +120,18 @@ export function AppearanceForm() { description: (err.response?.data as any)?.message || err.message, }) }, - }); + }) function onSubmit(data: AppearanceFormValues) { - i18n.changeLanguage(data.language); - setTheme(data.theme); - mutation.mutate(data); + i18n.changeLanguage(data.language) + setTheme(data.theme as Theme) + mutation.mutate(data) } return (
- + )} /> + ( - - {t('settings.appearance.field.theme')} - - {t('settings.appearance.description.theme')} - - + +
+ {t('settings.appearance.field.theme')} + {t('settings.appearance.description.theme')} +
{ + setTheme(v as Theme); + field.onChange(v) + }} defaultValue={field.value} - className='grid max-w-md grid-cols-2 gap-8 pt-2' + className='grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4 pt-2' > - - - - - -
-
-
-
-
-
-
-
-
+ {[...LIGHT_THEMES, ...DARK_THEMES].map((item) => { + const selected = field.value === item.value + + return ( + { + setTheme(item.value as Theme) + field.onChange(item.value) + }} + className={cn( + "cursor-pointer overflow-hidden border-2 transition-all hover:shadow-md", + selected ? "border-primary shadow-md" : "border-muted opacity-80" + )} + > +
+ + +
+ + {t(item.labelKey)} + + + {selected && ( + + )}
-
- - {t('settings.appearance.theme.light')} - - - - - - - - -
-
-
-
-
-
-
-
-
-
-
-
- - {t('settings.appearance.theme.dark')} - - - + + ) + })} + )} /> diff --git a/web/src/index.css b/web/src/index.css index e406f68..db0797c 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -1,101 +1,12 @@ +@import "./theme.css"; @tailwind base; @tailwind components; @tailwind utilities; + @layer base { - :root { - --background: 0 0% 100%; - --foreground: 215 28% 17%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215 16% 47%; - - --popover: 0 0% 100%; - --popover-foreground: 215 28% 17%; - --card: 0 0% 100%; - --card-foreground: 215 28% 17%; - --border: 214 32% 88%; - --input: 214 32% 88%; - - --primary: 221 83% 53%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222 47% 11%; - --accent: 210 40% 96.1%; - --accent-foreground: 222 47% 11%; - - --destructive: 0 84% 60%; - --destructive-foreground: 210 40% 98%; - - --ring: 221 83% 53%; - --radius: 0.35rem; - - --chart-1: 221 83% 53%; - --chart-2: 200 90% 40%; - --chart-3: 35 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - - --sidebar-background: 210 40% 98%; - --sidebar-foreground: 215 28% 17%; - --sidebar-primary: 221 83% 53%; - --sidebar-primary-foreground: 210 40% 98%; - --sidebar-accent: 214 32% 91%; - --sidebar-accent-foreground: 215 28% 17%; - --sidebar-border: 214 32% 88%; - --sidebar-ring: 221 83% 53%; - } - - .dark { - --background: 215 20% 12%; - --foreground: 210 40% 98%; - - --card: 215 20% 16%; - --card-foreground: 210 40% 98%; - --popover: 215 20% 16%; - --popover-foreground: 210 40% 98%; - - - --muted: 215 20% 22%; - --muted-foreground: 215 15% 70%; - - --border: 215 20% 28%; - --input: 215 20% 28%; - - --primary: 217 90% 70%; - --primary-foreground: 215 20% 12%; - - --secondary: 215 20% 22%; - --secondary-foreground: 210 40% 98%; - --accent: 215 20% 22%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 70% 60%; - --destructive-foreground: 210 40% 98%; - - --ring: 217 90% 70%; - - --chart-1: 217 90% 70%; - --chart-2: 200 90% 60%; - --chart-3: 35 90% 65%; - --chart-4: 280 75% 75%; - --chart-5: 340 85% 70%; - - --sidebar-background: 215 20% 10%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 217 90% 70%; - --sidebar-primary-foreground: 215 20% 12%; - --sidebar-accent: 215 20% 22%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 215 20% 28%; - --sidebar-ring: 217 90% 70%; - } - - - /* styles.css */ .CollapsibleContent { overflow: hidden; } diff --git a/web/src/theme.css b/web/src/theme.css new file mode 100644 index 0000000..42ec9e8 --- /dev/null +++ b/web/src/theme.css @@ -0,0 +1,452 @@ +/* theme.css */ +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 215 28% 17%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215 16% 47%; + + --popover: 0 0% 100%; + --popover-foreground: 215 28% 17%; + --card: 0 0% 100%; + --card-foreground: 215 28% 17%; + + --border: 214 32% 88%; + --input: 214 32% 88%; + + --primary: 221 83% 53%; + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222 47% 11%; + --accent: 210 40% 96.1%; + --accent-foreground: 222 47% 11%; + + --destructive: 0 84% 60%; + --destructive-foreground: 210 40% 98%; + + --ring: 221 83% 53%; + --radius: 0.35rem; + + --chart-1: 221 83% 53%; + --chart-2: 200 90% 40%; + --chart-3: 35 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .dark { + --background: 215 20% 12%; + --foreground: 210 40% 98%; + + --card: 215 20% 16%; + --card-foreground: 210 40% 98%; + --popover: 215 20% 16%; + --popover-foreground: 210 40% 98%; + + + --muted: 215 20% 22%; + --muted-foreground: 215 15% 70%; + + --border: 215 20% 28%; + --input: 215 20% 28%; + + --primary: 217 90% 70%; + --primary-foreground: 215 20% 12%; + + --secondary: 215 20% 22%; + --secondary-foreground: 210 40% 98%; + --accent: 215 20% 22%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 70% 60%; + --destructive-foreground: 210 40% 98%; + + --ring: 217 90% 70%; + + --chart-1: 217 90% 70%; + --chart-2: 200 90% 60%; + --chart-3: 35 90% 65%; + --chart-4: 280 75% 75%; + --chart-5: 340 85% 70%; + + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .yellow-light { + --radius: 0.5rem; + --background: 180 0% 100%; + --foreground: 49 5% 15%; + --muted: 106 10% 97%; + --muted-foreground: 58 5% 55%; + --popover: 180 0% 100%; + --popover-foreground: 49 5% 15%; + --card: 180 0% 100%; + --card-foreground: 49 5% 15%; + --border: 49 3% 92%; + --input: 49 3% 92%; + --primary: 48 100% 76%; + --primary-foreground: 54 20% 28%; + --secondary: 106 10% 97%; + --secondary-foreground: 56 10% 22%; + --accent: 106 10% 97%; + --accent-foreground: 56 10% 22%; + --destructive: 2 65% 55%; + --destructive-foreground: 106 10% 98%; + --ring: 49 5% 15%; + --chart-1: 15 65% 55%; + --chart-2: 183 45% 50%; + --chart-3: 225 35% 35%; + --chart-4: 88 55% 75%; + --chart-5: 45 60% 65%; + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .yellow-dark { + --radius: 0.5rem; + --background: 49 10% 20%; + --foreground: 106 10% 98%; + --muted: 38 10% 25%; + --muted-foreground: 56 10% 74%; + --popover: 49 10% 23%; + --popover-foreground: 106 10% 98%; + --card: 49 10% 23%; + --card-foreground: 106 10% 98%; + --border: 36 10% 30%; + --input: 36 10% 30%; + --primary: 48 100% 76%; + --primary-foreground: 54 20% 28%; + --secondary: 38 10% 25%; + --secondary-foreground: 106 10% 98%; + --accent: 38 10% 25%; + --accent-foreground: 106 10% 98%; + --destructive: 2 60% 45%; + --destructive-foreground: 106 10% 98%; + --ring: 66 65% 60%; + --chart-1: 262 55% 65%; + --chart-2: 165 40% 70%; + --chart-3: 60 50% 75%; + --chart-4: 312 50% 65%; + --chart-5: 6 55% 65%; + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .rose-light { + --radius: 0.5rem; + --background: 180 0% 100%; + --foreground: 285 5% 15%; + --muted: 286 1% 97%; + --muted-foreground: 286 7% 55%; + --popover: 180 0% 100%; + --popover-foreground: 285 5% 15%; + --card: 180 0% 100%; + --card-foreground: 285 5% 15%; + --border: 286 3% 92%; + --input: 286 3% 92%; + --primary: 350 55% 45%; + --primary-foreground: 355 25% 97%; + --secondary: 286 1% 97%; + --secondary-foreground: 286 5% 21%; + --accent: 286 1% 97%; + --accent-foreground: 286 5% 21%; + --destructive: 2 65% 55%; + --destructive-foreground: 180 0% 98.5%; + --ring: 350 55% 45%; + --chart-1: 15 65% 55%; + --chart-2: 183 45% 50%; + --chart-3: 225 35% 35%; + --chart-4: 88 55% 75%; + --chart-5: 45 60% 65%; + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .rose-dark { + --radius: 0.5rem; + --background: 49 5% 20.5%; + --foreground: 180 0% 96%; + --muted: 168 5% 31%; + --muted-foreground: 286 7% 75%; + --popover: 180 3% 24%; + --popover-foreground: 180 0% 96%; + --card: 56 6% 25%; + --card-foreground: 180 0% 96%; + --border: 286 5% 33%; + --input: 286 5% 33%; + --primary: 350 55% 45%; + --primary-foreground: 355 25% 97%; + --secondary: 286 5% 31%; + --secondary-foreground: 180 0% 98.5%; + --accent: 34 5% 31%; + --accent-foreground: 180 0% 98.5%; + --destructive: 2 60% 45%; + --destructive-foreground: 350 50% 97%; + --ring: 350 60% 50%; + --chart-1: 262 55% 65%; + --chart-2: 165 40% 70%; + --chart-3: 60 50% 75%; + --chart-4: 312 50% 65%; + --chart-5: 6 55% 65%; + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .orange-light { + --radius: 0.5rem; + --background: 180 0% 100%; + --foreground: 49 5% 15%; + --muted: 106 1% 97%; + --muted-foreground: 58 5% 55%; + --popover: 180 0% 100%; + --popover-foreground: 49 5% 15%; + --card: 180 0% 100%; + --card-foreground: 49 5% 15%; + --border: 49 3% 92%; + --input: 49 3% 92%; + --primary: 25 85% 58%; + --primary-foreground: 106 1% 98.5%; + --secondary: 106 1% 97%; + --secondary-foreground: 56 10% 22%; + --accent: 106 1% 97%; + --accent-foreground: 56 10% 22%; + --destructive: 2 65% 55%; + --destructive-foreground: 106 1% 98.5%; + --ring: 25 85% 58%; + --chart-1: 15 65% 55%; + --chart-2: 183 45% 50%; + --chart-3: 225 35% 35%; + --chart-4: 88 55% 75%; + --chart-5: 45 60% 65%; + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .orange-dark { + --radius: 0.5rem; + --background: 49 10% 20.5%; + --foreground: 106 1% 98.5%; + --muted: 34 10% 29%; + --muted-foreground: 56 10% 74%; + --popover: 49 10% 23.5%; + --popover-foreground: 106 1% 98.5%; + --card: 49 10% 23.5%; + --card-foreground: 106 1% 98.5%; + --border: 34 10% 32%; + --input: 34 10% 32%; + --primary: 22 80% 55%; + --primary-foreground: 106 1% 98.5%; + --secondary: 34 10% 29%; + --secondary-foreground: 106 1% 98.5%; + --accent: 34 10% 29%; + --accent-foreground: 106 1% 98.5%; + --destructive: 5 65% 55%; + --destructive-foreground: 106 1% 98.5%; + --ring: 22 75% 62%; + --chart-1: 262 55% 65%; + --chart-2: 165 40% 70%; + --chart-3: 60 50% 75%; + --chart-4: 312 50% 65%; + --chart-5: 6 55% 65%; + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .green-light { + --radius: 0.5rem; + --background: 180 0% 100%; + --foreground: 285 5% 14%; + --muted: 286 1% 97%; + --muted-foreground: 286 7% 55%; + --popover: 180 0% 100%; + --popover-foreground: 285 5% 14%; + --card: 180 0% 100%; + --card-foreground: 285 5% 14%; + --border: 286 3% 92%; + --input: 286 3% 92%; + --primary: 149 55% 48%; + --primary-foreground: 12 25% 97%; + --secondary: 286 1% 97%; + --secondary-foreground: 286 5% 21%; + --accent: 286 1% 97%; + --accent-foreground: 286 5% 21%; + --destructive: 2 65% 55%; + --destructive-foreground: 180 0% 98.5%; + --ring: 149 55% 48%; + --chart-1: 15 65% 55%; + --chart-2: 183 45% 50%; + --chart-3: 225 35% 35%; + --chart-4: 88 55% 75%; + --chart-5: 45 60% 65%; + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .green-dark { + --radius: 0.5rem; + --background: 49 10% 20.5%; + --foreground: 180 0% 96%; + --muted: 168 5% 31%; + --muted-foreground: 286 7% 75%; + --popover: 180 3% 24%; + --popover-foreground: 180 0% 96%; + --card: 56 6% 25%; + --card-foreground: 180 0% 96%; + --border: 286 5% 33%; + --input: 286 5% 33%; + --primary: 150 65% 65%; + --primary-foreground: 153 35% 20%; + --secondary: 286 5% 33%; + --secondary-foreground: 180 0% 98.5%; + --accent: 34 5% 31%; + --accent-foreground: 180 0% 98.5%; + --destructive: 2 60% 45%; + --destructive-foreground: 17 50% 97%; + --ring: 150 55% 55%; + --chart-1: 262 55% 65%; + --chart-2: 165 40% 70%; + --chart-3: 60 50% 75%; + --chart-4: 312 50% 65%; + --chart-5: 6 55% 65%; + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .blue-light { + --radius: 0.5rem; + --background: 180 0% 100%; + --foreground: 222 47% 11%; + --muted: 210 20% 96%; + --muted-foreground: 215 25% 40%; + --popover: 180 0% 100%; + --popover-foreground: 222 47% 11%; + --card: 180 0% 100%; + --card-foreground: 222 47% 11%; + --border: 214 32% 91%; + --input: 214 32% 91%; + --primary: 221 83% 53%; /* 经典的 Shadcn 蓝色 */ + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96%; + --secondary-foreground: 222 47% 11%; + --accent: 210 40% 96%; + --accent-foreground: 222 47% 11%; + --destructive: 0 84% 60%; + --destructive-foreground: 210 40% 98%; + --ring: 221 83% 53%; + --chart-1: 221 83% 53%; + --chart-2: 199 89% 48%; + --chart-3: 225 35% 35%; + --chart-4: 201 96% 32%; + --chart-5: 190 90% 40%; + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } + + .blue-dark { + --radius: 0.5rem; + --background: 222 47% 11%; + --foreground: 213 31% 91%; + --muted: 223 47% 11%; + --muted-foreground: 215 20% 65%; + --popover: 222 47% 11%; + --popover-foreground: 213 31% 91%; + --card: 222 47% 11%; + --card-foreground: 213 31% 91%; + --border: 216 34% 17%; + --input: 216 34% 17%; + --primary: 217 91% 60%; + --primary-foreground: 222 47% 11%; + --secondary: 222 47% 11%; + --secondary-foreground: 213 31% 91%; + --accent: 216 34% 17%; + --accent-foreground: 213 31% 91%; + --destructive: 0 63% 31%; + --destructive-foreground: 210 40% 98%; + --ring: 224 76% 48%; + --chart-1: 221 83% 53%; + --chart-2: 165 40% 70%; + --chart-3: 60 50% 75%; + --chart-4: 210 50% 65%; + --chart-5: 200 55% 65%; + --sidebar: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); + } +} \ No newline at end of file