From c0524494e989ef07cb51425a844673a55f4c1af3 Mon Sep 17 00:00:00 2001 From: bivashy Date: Thu, 15 Jan 2026 02:01:41 +0500 Subject: [PATCH] Add theme selection, dark/light mode selection --- .../ui/dropdown-menu/DropdownMenu.vue | 19 ++ .../DropdownMenuCheckboxItem.vue | 39 ++++ .../ui/dropdown-menu/DropdownMenuContent.vue | 39 ++++ .../ui/dropdown-menu/DropdownMenuGroup.vue | 15 ++ .../ui/dropdown-menu/DropdownMenuItem.vue | 31 ++++ .../ui/dropdown-menu/DropdownMenuLabel.vue | 23 +++ .../dropdown-menu/DropdownMenuRadioGroup.vue | 21 +++ .../dropdown-menu/DropdownMenuRadioItem.vue | 40 ++++ .../dropdown-menu/DropdownMenuSeparator.vue | 23 +++ .../ui/dropdown-menu/DropdownMenuShortcut.vue | 17 ++ .../ui/dropdown-menu/DropdownMenuSub.vue | 18 ++ .../dropdown-menu/DropdownMenuSubContent.vue | 27 +++ .../dropdown-menu/DropdownMenuSubTrigger.vue | 30 +++ .../ui/dropdown-menu/DropdownMenuTrigger.vue | 17 ++ app/components/ui/dropdown-menu/index.ts | 16 ++ app/components/ui/internal/AppHeader.vue | 15 +- app/components/ui/internal/HeroSection.vue | 62 +++++++ app/components/ui/internal/ModeToggle.vue | 22 +++ app/components/ui/internal/ThemeSelector.vue | 149 +++++++++++++++ app/components/ui/provider/ThemeProvider.vue | 7 + app/components/ui/select/Select.vue | 19 ++ app/components/ui/select/SelectContent.vue | 51 ++++++ app/components/ui/select/SelectGroup.vue | 15 ++ app/components/ui/select/SelectItem.vue | 44 +++++ app/components/ui/select/SelectItemText.vue | 15 ++ app/components/ui/select/SelectLabel.vue | 17 ++ .../ui/select/SelectScrollDownButton.vue | 26 +++ .../ui/select/SelectScrollUpButton.vue | 26 +++ app/components/ui/select/SelectSeparator.vue | 19 ++ app/components/ui/select/SelectTrigger.vue | 33 ++++ app/components/ui/select/SelectValue.vue | 15 ++ app/components/ui/select/index.ts | 11 ++ app/composables/useTheme.ts | 111 ++++++++++++ app/layouts/default.vue | 38 ++-- app/pages/index.vue | 85 +++------ bun.lock | 3 + nuxt.config.ts | 13 +- package.json | 1 + public/example/jigokuraku.jpg | Bin 0 -> 93908 bytes public/example/jujutsukaisen.jpg | Bin 0 -> 119299 bytes public/example/okiraku.jpg | Bin 0 -> 99740 bytes public/example/oshi no ko.jpg | Bin 0 -> 173423 bytes public/example/shibou.jpg | Bin 0 -> 89319 bytes public/example/sousounofrieren.jpg | Bin 0 -> 126776 bytes public/example/tamonkun.jpg | Bin 0 -> 115040 bytes public/example/yuusha party.jpg | Bin 0 -> 108772 bytes public/favicon-dark.ico | Bin 0 -> 221556 bytes public/favicon.ico | Bin 4286 -> 220290 bytes public/logo-dark.png | Bin 0 -> 109042 bytes public/logo.jpg | Bin 51730 -> 0 bytes public/logo.png | Bin 0 -> 110980 bytes public/themes/amber.css | 160 ++++++++++++++++ public/themes/amethyst.css | 160 ++++++++++++++++ public/themes/bold-tech.css | 160 ++++++++++++++++ public/themes/bubblegum.css | 160 ++++++++++++++++ public/themes/caffeine.css | 160 ++++++++++++++++ public/themes/candyland.css | 160 ++++++++++++++++ public/themes/catpuccin.css | 160 ++++++++++++++++ public/themes/claude.css | 160 ++++++++++++++++ public/themes/claymorphism.css | 160 ++++++++++++++++ public/themes/cleanslate.css | 160 ++++++++++++++++ public/themes/cosmicnight.css | 160 ++++++++++++++++ public/themes/cyberpunk.css | 160 ++++++++++++++++ public/themes/darkmatter.css | 171 ++++++++++++++++++ public/themes/default.css | 160 ++++++++++++++++ public/themes/doom64.css | 160 ++++++++++++++++ public/themes/eleganyluxury.css | 160 ++++++++++++++++ public/themes/graphite.css | 160 ++++++++++++++++ public/themes/kodama.css | 160 ++++++++++++++++ public/themes/midnight.css | 160 ++++++++++++++++ public/themes/mocha.css | 160 ++++++++++++++++ public/themes/modern.css | 160 ++++++++++++++++ public/themes/mono.css | 160 ++++++++++++++++ public/themes/nature.css | 160 ++++++++++++++++ public/themes/neo.css | 160 ++++++++++++++++ public/themes/northern.css | 160 ++++++++++++++++ public/themes/notebook.css | 171 ++++++++++++++++++ public/themes/ocean.css | 160 ++++++++++++++++ public/themes/pastel.css | 160 ++++++++++++++++ public/themes/perpetuity.css | 160 ++++++++++++++++ public/themes/quantum.css | 160 ++++++++++++++++ public/themes/retro.css | 160 ++++++++++++++++ public/themes/sage.css | 160 ++++++++++++++++ public/themes/softpop.css | 171 ++++++++++++++++++ public/themes/solardusk.css | 160 ++++++++++++++++ public/themes/starry.css | 160 ++++++++++++++++ public/themes/sunset.css | 160 ++++++++++++++++ public/themes/supabase.css | 171 ++++++++++++++++++ public/themes/t3chat.css | 160 ++++++++++++++++ public/themes/tangerine.css | 160 ++++++++++++++++ public/themes/twitter.css | 160 ++++++++++++++++ public/themes/vercel.css | 160 ++++++++++++++++ public/themes/vintage.css | 160 ++++++++++++++++ public/themes/violet.css | 171 ++++++++++++++++++ 94 files changed, 8035 insertions(+), 72 deletions(-) create mode 100644 app/components/ui/dropdown-menu/DropdownMenu.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuContent.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuGroup.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuItem.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuLabel.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuSeparator.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuShortcut.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuSub.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuSubContent.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue create mode 100644 app/components/ui/dropdown-menu/DropdownMenuTrigger.vue create mode 100644 app/components/ui/dropdown-menu/index.ts create mode 100644 app/components/ui/internal/HeroSection.vue create mode 100644 app/components/ui/internal/ModeToggle.vue create mode 100644 app/components/ui/internal/ThemeSelector.vue create mode 100644 app/components/ui/provider/ThemeProvider.vue create mode 100644 app/components/ui/select/Select.vue create mode 100644 app/components/ui/select/SelectContent.vue create mode 100644 app/components/ui/select/SelectGroup.vue create mode 100644 app/components/ui/select/SelectItem.vue create mode 100644 app/components/ui/select/SelectItemText.vue create mode 100644 app/components/ui/select/SelectLabel.vue create mode 100644 app/components/ui/select/SelectScrollDownButton.vue create mode 100644 app/components/ui/select/SelectScrollUpButton.vue create mode 100644 app/components/ui/select/SelectSeparator.vue create mode 100644 app/components/ui/select/SelectTrigger.vue create mode 100644 app/components/ui/select/SelectValue.vue create mode 100644 app/components/ui/select/index.ts create mode 100644 app/composables/useTheme.ts create mode 100644 public/example/jigokuraku.jpg create mode 100644 public/example/jujutsukaisen.jpg create mode 100644 public/example/okiraku.jpg create mode 100644 public/example/oshi no ko.jpg create mode 100644 public/example/shibou.jpg create mode 100644 public/example/sousounofrieren.jpg create mode 100644 public/example/tamonkun.jpg create mode 100644 public/example/yuusha party.jpg create mode 100644 public/favicon-dark.ico create mode 100644 public/logo-dark.png delete mode 100644 public/logo.jpg create mode 100644 public/logo.png create mode 100644 public/themes/amber.css create mode 100644 public/themes/amethyst.css create mode 100644 public/themes/bold-tech.css create mode 100644 public/themes/bubblegum.css create mode 100644 public/themes/caffeine.css create mode 100644 public/themes/candyland.css create mode 100644 public/themes/catpuccin.css create mode 100644 public/themes/claude.css create mode 100644 public/themes/claymorphism.css create mode 100644 public/themes/cleanslate.css create mode 100644 public/themes/cosmicnight.css create mode 100644 public/themes/cyberpunk.css create mode 100644 public/themes/darkmatter.css create mode 100644 public/themes/default.css create mode 100644 public/themes/doom64.css create mode 100644 public/themes/eleganyluxury.css create mode 100644 public/themes/graphite.css create mode 100644 public/themes/kodama.css create mode 100644 public/themes/midnight.css create mode 100644 public/themes/mocha.css create mode 100644 public/themes/modern.css create mode 100644 public/themes/mono.css create mode 100644 public/themes/nature.css create mode 100644 public/themes/neo.css create mode 100644 public/themes/northern.css create mode 100644 public/themes/notebook.css create mode 100644 public/themes/ocean.css create mode 100644 public/themes/pastel.css create mode 100644 public/themes/perpetuity.css create mode 100644 public/themes/quantum.css create mode 100644 public/themes/retro.css create mode 100644 public/themes/sage.css create mode 100644 public/themes/softpop.css create mode 100644 public/themes/solardusk.css create mode 100644 public/themes/starry.css create mode 100644 public/themes/sunset.css create mode 100644 public/themes/supabase.css create mode 100644 public/themes/t3chat.css create mode 100644 public/themes/tangerine.css create mode 100644 public/themes/twitter.css create mode 100644 public/themes/vercel.css create mode 100644 public/themes/vintage.css create mode 100644 public/themes/violet.css diff --git a/app/components/ui/dropdown-menu/DropdownMenu.vue b/app/components/ui/dropdown-menu/DropdownMenu.vue new file mode 100644 index 0000000..e1c9ee3 --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenu.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue b/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue new file mode 100644 index 0000000..1253078 --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuContent.vue b/app/components/ui/dropdown-menu/DropdownMenuContent.vue new file mode 100644 index 0000000..7c43014 --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuContent.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuGroup.vue b/app/components/ui/dropdown-menu/DropdownMenuGroup.vue new file mode 100644 index 0000000..da634ec --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuGroup.vue @@ -0,0 +1,15 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuItem.vue b/app/components/ui/dropdown-menu/DropdownMenuItem.vue new file mode 100644 index 0000000..f56cae3 --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuItem.vue @@ -0,0 +1,31 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuLabel.vue b/app/components/ui/dropdown-menu/DropdownMenuLabel.vue new file mode 100644 index 0000000..8bca83c --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuLabel.vue @@ -0,0 +1,23 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue b/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue new file mode 100644 index 0000000..fe82cad --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue @@ -0,0 +1,21 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue b/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue new file mode 100644 index 0000000..e03c40c --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue @@ -0,0 +1,40 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue b/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue new file mode 100644 index 0000000..1b936c3 --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue @@ -0,0 +1,23 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue b/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue new file mode 100644 index 0000000..60be75c --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue @@ -0,0 +1,17 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuSub.vue b/app/components/ui/dropdown-menu/DropdownMenuSub.vue new file mode 100644 index 0000000..7472e77 --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuSub.vue @@ -0,0 +1,18 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue b/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue new file mode 100644 index 0000000..d7c6b08 --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue @@ -0,0 +1,27 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue b/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue new file mode 100644 index 0000000..1683aaf --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue @@ -0,0 +1,30 @@ + + + diff --git a/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue b/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue new file mode 100644 index 0000000..75cd747 --- /dev/null +++ b/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue @@ -0,0 +1,17 @@ + + + diff --git a/app/components/ui/dropdown-menu/index.ts b/app/components/ui/dropdown-menu/index.ts new file mode 100644 index 0000000..955fe3a --- /dev/null +++ b/app/components/ui/dropdown-menu/index.ts @@ -0,0 +1,16 @@ +export { default as DropdownMenu } from "./DropdownMenu.vue" + +export { default as DropdownMenuCheckboxItem } from "./DropdownMenuCheckboxItem.vue" +export { default as DropdownMenuContent } from "./DropdownMenuContent.vue" +export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue" +export { default as DropdownMenuItem } from "./DropdownMenuItem.vue" +export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue" +export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue" +export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue" +export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue" +export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue" +export { default as DropdownMenuSub } from "./DropdownMenuSub.vue" +export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue" +export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue" +export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue" +export { DropdownMenuPortal } from "reka-ui" diff --git a/app/components/ui/internal/AppHeader.vue b/app/components/ui/internal/AppHeader.vue index 671faeb..42ac8ce 100644 --- a/app/components/ui/internal/AppHeader.vue +++ b/app/components/ui/internal/AppHeader.vue @@ -1,11 +1,20 @@ diff --git a/app/components/ui/internal/HeroSection.vue b/app/components/ui/internal/HeroSection.vue new file mode 100644 index 0000000..e53910a --- /dev/null +++ b/app/components/ui/internal/HeroSection.vue @@ -0,0 +1,62 @@ + diff --git a/app/components/ui/internal/ModeToggle.vue b/app/components/ui/internal/ModeToggle.vue new file mode 100644 index 0000000..c009022 --- /dev/null +++ b/app/components/ui/internal/ModeToggle.vue @@ -0,0 +1,22 @@ + + + diff --git a/app/components/ui/internal/ThemeSelector.vue b/app/components/ui/internal/ThemeSelector.vue new file mode 100644 index 0000000..0f916a9 --- /dev/null +++ b/app/components/ui/internal/ThemeSelector.vue @@ -0,0 +1,149 @@ + + + diff --git a/app/components/ui/provider/ThemeProvider.vue b/app/components/ui/provider/ThemeProvider.vue new file mode 100644 index 0000000..207dc1a --- /dev/null +++ b/app/components/ui/provider/ThemeProvider.vue @@ -0,0 +1,7 @@ + + + diff --git a/app/components/ui/select/Select.vue b/app/components/ui/select/Select.vue new file mode 100644 index 0000000..c94bbe8 --- /dev/null +++ b/app/components/ui/select/Select.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/components/ui/select/SelectContent.vue b/app/components/ui/select/SelectContent.vue new file mode 100644 index 0000000..adf04ec --- /dev/null +++ b/app/components/ui/select/SelectContent.vue @@ -0,0 +1,51 @@ + + + diff --git a/app/components/ui/select/SelectGroup.vue b/app/components/ui/select/SelectGroup.vue new file mode 100644 index 0000000..e981c6c --- /dev/null +++ b/app/components/ui/select/SelectGroup.vue @@ -0,0 +1,15 @@ + + + diff --git a/app/components/ui/select/SelectItem.vue b/app/components/ui/select/SelectItem.vue new file mode 100644 index 0000000..9371764 --- /dev/null +++ b/app/components/ui/select/SelectItem.vue @@ -0,0 +1,44 @@ + + + diff --git a/app/components/ui/select/SelectItemText.vue b/app/components/ui/select/SelectItemText.vue new file mode 100644 index 0000000..b6700b1 --- /dev/null +++ b/app/components/ui/select/SelectItemText.vue @@ -0,0 +1,15 @@ + + + diff --git a/app/components/ui/select/SelectLabel.vue b/app/components/ui/select/SelectLabel.vue new file mode 100644 index 0000000..5b6650c --- /dev/null +++ b/app/components/ui/select/SelectLabel.vue @@ -0,0 +1,17 @@ + + + diff --git a/app/components/ui/select/SelectScrollDownButton.vue b/app/components/ui/select/SelectScrollDownButton.vue new file mode 100644 index 0000000..7dc7670 --- /dev/null +++ b/app/components/ui/select/SelectScrollDownButton.vue @@ -0,0 +1,26 @@ + + + diff --git a/app/components/ui/select/SelectScrollUpButton.vue b/app/components/ui/select/SelectScrollUpButton.vue new file mode 100644 index 0000000..07fe87e --- /dev/null +++ b/app/components/ui/select/SelectScrollUpButton.vue @@ -0,0 +1,26 @@ + + + diff --git a/app/components/ui/select/SelectSeparator.vue b/app/components/ui/select/SelectSeparator.vue new file mode 100644 index 0000000..4b5c885 --- /dev/null +++ b/app/components/ui/select/SelectSeparator.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/components/ui/select/SelectTrigger.vue b/app/components/ui/select/SelectTrigger.vue new file mode 100644 index 0000000..667908b --- /dev/null +++ b/app/components/ui/select/SelectTrigger.vue @@ -0,0 +1,33 @@ + + + diff --git a/app/components/ui/select/SelectValue.vue b/app/components/ui/select/SelectValue.vue new file mode 100644 index 0000000..d5ce58b --- /dev/null +++ b/app/components/ui/select/SelectValue.vue @@ -0,0 +1,15 @@ + + + diff --git a/app/components/ui/select/index.ts b/app/components/ui/select/index.ts new file mode 100644 index 0000000..96eae60 --- /dev/null +++ b/app/components/ui/select/index.ts @@ -0,0 +1,11 @@ +export { default as Select } from "./Select.vue" +export { default as SelectContent } from "./SelectContent.vue" +export { default as SelectGroup } from "./SelectGroup.vue" +export { default as SelectItem } from "./SelectItem.vue" +export { default as SelectItemText } from "./SelectItemText.vue" +export { default as SelectLabel } from "./SelectLabel.vue" +export { default as SelectScrollDownButton } from "./SelectScrollDownButton.vue" +export { default as SelectScrollUpButton } from "./SelectScrollUpButton.vue" +export { default as SelectSeparator } from "./SelectSeparator.vue" +export { default as SelectTrigger } from "./SelectTrigger.vue" +export { default as SelectValue } from "./SelectValue.vue" diff --git a/app/composables/useTheme.ts b/app/composables/useTheme.ts new file mode 100644 index 0000000..c33d8ec --- /dev/null +++ b/app/composables/useTheme.ts @@ -0,0 +1,111 @@ +export type ColorScheme = 'dark' | 'light' | 'system' +export type ThemePreset = + | 'default' | 'darkmatter' | 'cyberpunk' | 'claymorphism' | 'cleanslate' + | 'modern' | 'nature' | 'mocha' | 'graphite' | 'eleganyluxury' + | 'kodama' | 'midnight' | 'mono' | 'catpuccin' | 'claude' + | 'cosmicnight' | 'doom64' | 'amber' | 'amethyst' | 'bold-tech' + | 'bubblegum' | 'caffeine' | 'candyland' | 'neo' | 'northern' + | 'notebook' | 'ocean' | 'pastel' | 'perpetuity' | 'quantum' + | 'retro' | 'sage' | 'softpop' | 'solardusk' | 'starry' + | 'sunset' | 'supabase' | 't3chat' | 'tangerine' | 'twitter' + | 'vercel' | 'vintage' | 'violet' + +interface ThemeState { + colorScheme: ColorScheme + themePreset: ThemePreset +} + +const DEFAULT_COLOR_SCHEME: ColorScheme = 'system' +const DEFAULT_THEME_PRESET: ThemePreset = 'default' +const COLOR_SCHEME_KEY = 'vite-ui-color-scheme' +const THEME_PRESET_KEY = 'vite-ui-theme-preset' + +export const useTheme = () => { + const colorScheme = ref(DEFAULT_COLOR_SCHEME) + const themePreset = ref(DEFAULT_THEME_PRESET) + + const initFromStorage = () => { + if (import.meta.client) { + colorScheme.value = (localStorage.getItem(COLOR_SCHEME_KEY) as ColorScheme) || DEFAULT_COLOR_SCHEME + themePreset.value = (localStorage.getItem(THEME_PRESET_KEY) as ThemePreset) || DEFAULT_THEME_PRESET + applyTheme() + } + } + + const applyTheme = () => { + if (!import.meta.client) return + + const root = document.documentElement + root.classList.remove('light', 'dark') + + let effectiveScheme = colorScheme.value + if (effectiveScheme === 'system') { + effectiveScheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + } + + root.classList.add(effectiveScheme) + + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = `/themes/${themePreset.value}.css` + link.title = 'theme-preset' + link.id = 'theme-preset-stylesheet' + + link.onload = () => { + document.querySelectorAll('link[title="theme-preset"]').forEach(oldLink => { + if (oldLink.id !== 'theme-preset-stylesheet') { + document.head.removeChild(oldLink) + } + }) + } + + const existingLink = document.getElementById('theme-preset-stylesheet') + if (existingLink) { + existingLink.remove() + } + + document.head.appendChild(link) + } + + const watchSystemTheme = () => { + if (!import.meta.client) return + + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') + const handleChange = () => { + if (colorScheme.value === 'system') { + applyTheme() + } + } + + mediaQuery.addEventListener('change', handleChange) + return () => mediaQuery.removeEventListener('change', handleChange) + } + + const setColorScheme = (scheme: ColorScheme) => { + colorScheme.value = scheme + if (import.meta.client) { + localStorage.setItem(COLOR_SCHEME_KEY, scheme) + applyTheme() + } + } + + const setThemePreset = (preset: ThemePreset) => { + themePreset.value = preset + if (import.meta.client) { + localStorage.setItem(THEME_PRESET_KEY, preset) + applyTheme() + } + } + + onMounted(() => { + initFromStorage() + watchSystemTheme() + }) + + return { + colorScheme: readonly(colorScheme), + themePreset: readonly(themePreset), + setColorScheme, + setThemePreset + } +} diff --git a/app/layouts/default.vue b/app/layouts/default.vue index a2c4502..c78dd4e 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -1,15 +1,17 @@