mirror of
https://github.com/fmhy/edit.git
synced 2026-03-11 08:55:38 +00:00
This reverts commit a71bb4c0d0.
This commit is contained in:
parent
a71bb4c0d0
commit
daf15a1a8d
11 changed files with 29 additions and 1013 deletions
|
|
@ -207,13 +207,6 @@ export default defineConfig({
|
|||
build: {
|
||||
// Shut the fuck up
|
||||
chunkSizeWarningLimit: Number.POSITIVE_INFINITY
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
markdown: {
|
||||
|
|
|
|||
|
|
@ -1,37 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import { colors } from '@fmhy/colors'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { watch, onMounted, nextTick, ref, computed } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
import { watch, onMounted, nextTick } from 'vue'
|
||||
import { useTheme } from '../themes/themeHandler'
|
||||
import { themeRegistry } from '../themes/configs'
|
||||
import type { Theme } from '../themes/types'
|
||||
import CustomColorSelector from './CustomColorSelector.vue'
|
||||
import tinycolor from 'tinycolor2'
|
||||
import Switch from './Switch.vue'
|
||||
|
||||
type ColorNames = keyof typeof colors
|
||||
const selectedColor = useStorage<ColorNames>('preferred-color', 'swarm')
|
||||
|
||||
const { frontmatter, page } = useData()
|
||||
|
||||
const showPalette = computed(() => {
|
||||
// console.log('Current layout:', frontmatter.value.layout)
|
||||
// console.log('Page relative path:', page.value.relativePath)
|
||||
return frontmatter.value.layout !== 'home' && page.value.relativePath !== 'index.md'
|
||||
})
|
||||
|
||||
// Use the theme system
|
||||
const { amoledEnabled, setAmoledEnabled, setTheme, setMode, mode, themeName, restorePreviousMode } = useTheme()
|
||||
|
||||
// Custom color selector state
|
||||
const showCustomColorSelector = ref(false)
|
||||
const { amoledEnabled, setAmoledEnabled, setTheme, state, mode, themeName } = useTheme()
|
||||
|
||||
const colorOptions = Object.keys(colors).filter(
|
||||
(key) => typeof colors[key as keyof typeof colors] === 'object'
|
||||
) as Array<ColorNames>
|
||||
|
||||
// Preset themes (exclude dynamically generated color- themes and custom theme)
|
||||
const presetThemeNames = Object.keys(themeRegistry).filter((k) => !k.startsWith('color-') && k !== 'custom')
|
||||
// Preset themes (exclude dynamically generated color- themes)
|
||||
const presetThemeNames = Object.keys(themeRegistry).filter((k) => !k.startsWith('color-'))
|
||||
|
||||
const getThemePreviewStyle = (name: string) => {
|
||||
const theme = themeRegistry[name]
|
||||
|
|
@ -209,217 +196,28 @@ const normalizeColorName = (colorName: string) =>
|
|||
colorName.slice(1).replaceAll(/-/g, ' ')
|
||||
|
||||
onMounted(async () => {
|
||||
// Check if custom theme was last selected
|
||||
const savedTheme = localStorage.getItem('vitepress-theme-name')
|
||||
|
||||
if (savedTheme === 'custom') {
|
||||
// Load saved custom colors
|
||||
const savedLinkColor = localStorage.getItem('custom-theme-link-color') || '#ffffff'
|
||||
const savedTextColor = localStorage.getItem('custom-theme-text-color') || '#cccccc'
|
||||
const savedBgColor = localStorage.getItem('custom-theme-bg-color') || '#000000'
|
||||
|
||||
// Apply the custom theme
|
||||
applyCustomColors({ link: savedLinkColor, text: savedTextColor, background: savedBgColor })
|
||||
} else if (selectedColor.value) {
|
||||
// apply saved color theme on load
|
||||
// apply saved theme on load
|
||||
if (selectedColor.value) {
|
||||
const theme = generateThemeFromColor(selectedColor.value)
|
||||
themeRegistry[`color-${selectedColor.value}`] = theme
|
||||
await nextTick()
|
||||
setTheme(`color-${selectedColor.value}`)
|
||||
}
|
||||
|
||||
// Wait for next tick to ensure theme handler is fully initialized
|
||||
await nextTick()
|
||||
})
|
||||
|
||||
watch(selectedColor, async (color) => {
|
||||
if (!color) return;
|
||||
// Restore previous mode when switching away from custom
|
||||
restorePreviousMode()
|
||||
const theme = generateThemeFromColor(color)
|
||||
themeRegistry[`color-${color}`] = theme
|
||||
await nextTick()
|
||||
setTheme(`color-${color}`)
|
||||
})
|
||||
|
||||
|
||||
|
||||
const openCustomColorSelector = () => {
|
||||
showCustomColorSelector.value = true
|
||||
const toggleAmoled = () => {
|
||||
setAmoledEnabled(!amoledEnabled.value)
|
||||
}
|
||||
|
||||
const applyCustomColors = (colors: { link: string; text: string; background: string }) => {
|
||||
// Store custom colors
|
||||
const customLinkColor = useStorage('custom-theme-link-color', colors.link)
|
||||
const customTextColor = useStorage('custom-theme-text-color', colors.text)
|
||||
const customBgColor = useStorage('custom-theme-bg-color', colors.background)
|
||||
|
||||
customLinkColor.value = colors.link
|
||||
customTextColor.value = colors.text
|
||||
customBgColor.value = colors.background
|
||||
|
||||
// Create lighter versions of background for cards
|
||||
// Increase lightening to make cards more distinct
|
||||
const lightenedBg = tinycolor(colors.background).lighten(10).toString()
|
||||
const lightenedBgAlt = tinycolor(colors.background).lighten(15).toString()
|
||||
|
||||
// Generate a custom theme - link color for links, text color for body text
|
||||
const customTheme: Theme = {
|
||||
name: 'custom',
|
||||
displayName: 'Custom',
|
||||
preview: colors.background,
|
||||
modes: {
|
||||
light: {
|
||||
brand: {
|
||||
1: colors.link, // Links will use this color
|
||||
2: colors.link,
|
||||
3: colors.link,
|
||||
soft: colors.link
|
||||
},
|
||||
bg: colors.background,
|
||||
bgAlt: lightenedBg,
|
||||
bgElv: lightenedBgAlt,
|
||||
text: {
|
||||
1: colors.text, // Body text uses this color
|
||||
2: colors.text,
|
||||
3: colors.text
|
||||
},
|
||||
button: {
|
||||
brand: {
|
||||
bg: colors.link,
|
||||
border: colors.link,
|
||||
text: colors.text,
|
||||
hoverBorder: colors.link,
|
||||
hoverText: colors.text,
|
||||
hoverBg: colors.link,
|
||||
activeBorder: colors.link,
|
||||
activeText: colors.text,
|
||||
activeBg: colors.link
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: colors.background,
|
||||
border: colors.link,
|
||||
text: colors.text,
|
||||
textDeep: colors.text
|
||||
},
|
||||
tip: {
|
||||
bg: '#D8F8E4',
|
||||
border: '#447A61',
|
||||
text: '#2D6A58',
|
||||
textDeep: '#166534'
|
||||
},
|
||||
warning: {
|
||||
bg: '#FCEFC3',
|
||||
border: '#9A8034',
|
||||
text: '#9C701B',
|
||||
textDeep: '#92400e'
|
||||
},
|
||||
danger: {
|
||||
bg: '#FBE1E2',
|
||||
border: '#B3565E',
|
||||
text: '#912239',
|
||||
textDeep: '#991b1b'
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
bg: colors.link
|
||||
},
|
||||
home: {
|
||||
heroNameColor: colors.link,
|
||||
heroNameBackground: colors.background,
|
||||
heroImageBackground: `linear-gradient(135deg, ${colors.background} 0%, ${colors.link} 100%)`,
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
},
|
||||
dark: {
|
||||
brand: {
|
||||
1: colors.link, // Links will use this color
|
||||
2: colors.link,
|
||||
3: colors.link,
|
||||
soft: colors.link
|
||||
},
|
||||
bg: colors.background,
|
||||
bgAlt: lightenedBg,
|
||||
bgElv: lightenedBgAlt,
|
||||
text: {
|
||||
1: colors.text, // Body text uses this color
|
||||
2: colors.text,
|
||||
3: colors.text
|
||||
},
|
||||
button: {
|
||||
brand: {
|
||||
bg: colors.link,
|
||||
border: colors.link,
|
||||
text: colors.text,
|
||||
hoverBorder: colors.link,
|
||||
hoverText: colors.text,
|
||||
hoverBg: colors.link,
|
||||
activeBorder: colors.link,
|
||||
activeText: colors.text,
|
||||
activeBg: colors.link
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: colors.background,
|
||||
border: colors.link,
|
||||
text: colors.text,
|
||||
textDeep: colors.text
|
||||
},
|
||||
tip: {
|
||||
bg: '#0C2A20',
|
||||
border: '#184633',
|
||||
text: '#B0EBC9',
|
||||
textDeep: '#166534'
|
||||
},
|
||||
warning: {
|
||||
bg: '#403207',
|
||||
border: '#7E6211',
|
||||
text: '#F9DE88',
|
||||
textDeep: '#92400e'
|
||||
},
|
||||
danger: {
|
||||
bg: '#3F060A',
|
||||
border: '#7C0F18',
|
||||
text: '#F7C1BC',
|
||||
textDeep: '#991b1b'
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
bg: colors.link
|
||||
},
|
||||
home: {
|
||||
heroNameColor: colors.link,
|
||||
heroNameBackground: colors.background,
|
||||
heroImageBackground: `linear-gradient(135deg, ${colors.background} 0%, ${colors.link} 100%)`,
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register and apply the custom theme
|
||||
themeRegistry['custom'] = customTheme
|
||||
selectedColor.value = '' as ColorNames
|
||||
setTheme('custom')
|
||||
// Auto-set custom mode
|
||||
setMode('custom')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -430,7 +228,7 @@ const applyCustomColors = (colors: { link: string; text: string; background: str
|
|||
<button
|
||||
:class="[
|
||||
'inline-block w-6 h-6 rounded-full transition-all duration-200 border-2',
|
||||
(themeName === `color-${color}`)
|
||||
(themeName && themeName.value === `color-${color}`)
|
||||
? 'border-slate-200 dark:border-slate-400 shadow-lg'
|
||||
: 'border-transparent'
|
||||
]"
|
||||
|
|
@ -449,11 +247,11 @@ const applyCustomColors = (colors: { link: string; text: string; background: str
|
|||
<button
|
||||
:class="[
|
||||
'inline-block w-6 h-6 rounded-full transition-all duration-200 border-2',
|
||||
(themeName === t)
|
||||
(themeName && themeName.value === t)
|
||||
? 'border-slate-200 dark:border-slate-400 shadow-lg'
|
||||
: 'border-transparent'
|
||||
]"
|
||||
@click="selectedColor = '' as ColorNames; restorePreviousMode(); setTheme(t)"
|
||||
@click="selectedColor = '' as ColorNames; setTheme(t)"
|
||||
:title="themeRegistry[t].displayName"
|
||||
>
|
||||
<span
|
||||
|
|
@ -462,32 +260,6 @@ const applyCustomColors = (colors: { link: string; text: string; background: str
|
|||
></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Custom theme button (after preset themes) -->
|
||||
<div v-if="showPalette">
|
||||
<button
|
||||
:class="[
|
||||
'inline-block w-6 h-6 rounded-full transition-all duration-200 border-2 relative overflow-hidden',
|
||||
(themeName === 'custom')
|
||||
? 'border-slate-200 dark:border-slate-400 shadow-lg'
|
||||
: 'border-transparent'
|
||||
]"
|
||||
@click="openCustomColorSelector"
|
||||
title="Custom Theme"
|
||||
>
|
||||
<span
|
||||
class="inline-block w-full h-full rounded-full bg-gradient-to-br from-purple-500 via-pink-500 to-red-500 flex items-center justify-center"
|
||||
>
|
||||
<div class="i-lucide-palette text-white text-xs" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Color Selector Modal -->
|
||||
<CustomColorSelector
|
||||
v-model="showCustomColorSelector"
|
||||
@apply="applyCustomColors"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,486 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import tinycolor from 'tinycolor2'
|
||||
|
||||
/* ================= PROPS / EMITS ================= */
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
}
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'apply', colors: { link: string; text: string; background: string }): void
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
|
||||
|
||||
/* ================= RGB STATE ================= */
|
||||
const linkR = ref(255)
|
||||
const linkG = ref(255)
|
||||
const linkB = ref(255)
|
||||
|
||||
const textR = ref(255)
|
||||
const textG = ref(255)
|
||||
const textB = ref(255)
|
||||
|
||||
const bgR = ref(0)
|
||||
const bgG = ref(0)
|
||||
const bgB = ref(0)
|
||||
|
||||
// Active tab for compact view
|
||||
const activeTab = ref<'link' | 'text' | 'bg'>('link')
|
||||
|
||||
/* ================= COLOR HELPERS ================= */
|
||||
const rgbToHex = (r:number,g:number,b:number) =>
|
||||
tinycolor({ r, g, b }).toHexString()
|
||||
|
||||
const hexToRgb = (hex:string) => {
|
||||
const c = tinycolor(hex)
|
||||
if (!c.isValid()) return null
|
||||
const { r, g, b } = c.toRgb()
|
||||
return { r, g, b }
|
||||
}
|
||||
|
||||
/* ================= PICKER HANDLERS ================= */
|
||||
// (Removed complex 2D picker handlers)
|
||||
|
||||
/* ================= HEX COMPUTED ================= */
|
||||
const makeHex = (r:any,g:any,b:any) => computed({
|
||||
get: () => rgbToHex(r.value, g.value, b.value),
|
||||
set: (val:string) => {
|
||||
const rgb = hexToRgb(val)
|
||||
if (!rgb) return
|
||||
r.value = rgb.r
|
||||
g.value = rgb.g
|
||||
b.value = rgb.b
|
||||
}
|
||||
})
|
||||
|
||||
const linkHex = makeHex(linkR, linkG, linkB)
|
||||
const textHex = makeHex(textR, textG, textB)
|
||||
const bgHex = makeHex(bgR, bgG, bgB)
|
||||
|
||||
/* ================= PREVIEW ================= */
|
||||
const linkColor = computed(() => rgbToHex(linkR.value, linkG.value, linkB.value))
|
||||
const textColor = computed(() => rgbToHex(textR.value, textG.value, textB.value))
|
||||
const bgColor = computed(() => rgbToHex(bgR.value, bgG.value, bgB.value))
|
||||
|
||||
/* ================= WARNINGS ================= */
|
||||
const linkBgContrast = computed(() => tinycolor.readability(linkColor.value, bgColor.value))
|
||||
const textBgContrast = computed(() => tinycolor.readability(textColor.value, bgColor.value))
|
||||
|
||||
const warnings = computed(() => {
|
||||
const list: string[] = []
|
||||
// WCAG AA for normal text is 4.5:1
|
||||
if (linkBgContrast.value < 4.5) {
|
||||
list.push('Warning: Low contrast between Link and Background')
|
||||
}
|
||||
if (textBgContrast.value < 4.5) {
|
||||
list.push('Warning: Low contrast between Text and Background')
|
||||
}
|
||||
return list
|
||||
})
|
||||
|
||||
/* ================= STORAGE ================= */
|
||||
const savedLink = useStorage('custom-theme-link-color', '#ffffff')
|
||||
const savedText = useStorage('custom-theme-text-color', '#cccccc')
|
||||
const savedBg = useStorage('custom-theme-bg-color', '#000000')
|
||||
|
||||
const initColors = () => {
|
||||
const a = hexToRgb(savedLink.value)
|
||||
const b = hexToRgb(savedText.value)
|
||||
const c = hexToRgb(savedBg.value)
|
||||
if (a) { linkR.value = a.r; linkG.value = a.g; linkB.value = a.b }
|
||||
if (b) { textR.value = b.r; textG.value = b.g; textB.value = b.b }
|
||||
if (c) { bgR.value = c.r; bgG.value = c.g; bgB.value = c.b }
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, v => v && initColors())
|
||||
|
||||
/* ================= ACTIONS ================= */
|
||||
const close = () => emit('update:modelValue', false)
|
||||
|
||||
const apply = () => {
|
||||
// Save to persistence
|
||||
savedLink.value = linkColor.value
|
||||
savedText.value = textColor.value
|
||||
savedBg.value = bgColor.value
|
||||
|
||||
emit('apply', {
|
||||
link: linkColor.value,
|
||||
text: textColor.value,
|
||||
background: bgColor.value
|
||||
})
|
||||
close()
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Transition name="modal">
|
||||
<div
|
||||
v-if="modelValue"
|
||||
class="fixed inset-0 z-999 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||
|
||||
>
|
||||
<div
|
||||
class="bg-$vp-c-bg border-$vp-c-default-soft relative w-full max-w-xl rounded-lg border-2 p-6 shadow-xl select-none"
|
||||
@click.stop
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h3 class="text-$vp-c-text-1 text-lg font-bold">Custom Theme</h3>
|
||||
<button
|
||||
class="text-$vp-c-text-2 hover:text-$vp-c-text-1 transition-colors"
|
||||
@click="close"
|
||||
aria-label="Close"
|
||||
>
|
||||
<div class="i-carbon-close h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Preview Section (Compact) -->
|
||||
<div class="mb-4 select-none">
|
||||
<div
|
||||
class="rounded-lg border-2 border-$vp-c-divider p-3 text-sm transition-all duration-300 select-none"
|
||||
:style="{ backgroundColor: bgColor, color: textColor }"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<div>
|
||||
<strong :style="{ color: linkColor }">⭐ AnimeKai</strong>
|
||||
<span :style="{ color: textColor }">, </span>
|
||||
<a href="#" :style="{ color: linkColor }" class="hover:underline">2</a>
|
||||
<span :style="{ color: textColor }">, </span>
|
||||
<a href="#" :style="{ color: linkColor }" class="hover:underline">3</a>
|
||||
<span :style="{ color: textColor }">, </span>
|
||||
<a href="#" :style="{ color: linkColor }" class="hover:underline">4</a>
|
||||
<span :style="{ color: textColor }">, </span>
|
||||
<a href="#" :style="{ color: linkColor }" class="hover:underline">5</a>
|
||||
<span :style="{ color: textColor }">, </span>
|
||||
<a href="#" :style="{ color: linkColor }" class="hover:underline">6</a>
|
||||
<span :style="{ color: textColor }">, </span>
|
||||
<a href="#" :style="{ color: linkColor }" class="hover:underline">7</a>
|
||||
<span :style="{ color: textColor }"> or </span>
|
||||
<strong :style="{ color: linkColor }">AniGo</strong>
|
||||
<span :style="{ color: textColor }"> - Hard Subs / Dub / Auto-Next</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong :style="{ color: linkColor }">⭐ Miruro</strong>
|
||||
<span :style="{ color: textColor }"> - Hard Subs / Dub / Auto-Next</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong :style="{ color: linkColor }">⭐ HiAnime</strong>
|
||||
<span :style="{ color: textColor }">, </span>
|
||||
<a href="#" :style="{ color: linkColor }" class="hover:underline">2</a>
|
||||
<span :style="{ color: textColor }">, </span>
|
||||
<a href="#" :style="{ color: linkColor }" class="hover:underline">3</a>
|
||||
<span :style="{ color: textColor }">, </span>
|
||||
<a href="#" :style="{ color: linkColor }" class="hover:underline">4</a>
|
||||
<span :style="{ color: textColor }">, </span>
|
||||
<a href="#" :style="{ color: linkColor }" class="hover:underline">5</a>
|
||||
<span :style="{ color: textColor }"> - Sub / Dub / Auto-Next</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="mb-4 flex gap-2">
|
||||
<button
|
||||
@click="activeTab = 'link'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-colors',
|
||||
activeTab === 'link'
|
||||
? ''
|
||||
: 'bg-$vp-c-bg-alt text-$vp-c-text-2 hover:text-$vp-c-text-1'
|
||||
]"
|
||||
:style="activeTab === 'link' ? { backgroundColor: linkColor, color: textColor } : {}"
|
||||
>
|
||||
Link
|
||||
</button>
|
||||
<button
|
||||
@click="activeTab = 'bg'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-colors',
|
||||
activeTab === 'bg'
|
||||
? ''
|
||||
: 'bg-$vp-c-bg-alt text-$vp-c-text-2 hover:text-$vp-c-text-1'
|
||||
]"
|
||||
:style="activeTab === 'bg' ? { backgroundColor: linkColor, color: textColor } : {}"
|
||||
>
|
||||
Background
|
||||
</button>
|
||||
<button
|
||||
@click="activeTab = 'text'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-colors',
|
||||
activeTab === 'text'
|
||||
? ''
|
||||
: 'bg-$vp-c-bg-alt text-$vp-c-text-2 hover:text-$vp-c-text-1'
|
||||
]"
|
||||
:style="activeTab === 'text' ? { backgroundColor: linkColor, color: textColor } : {}"
|
||||
>
|
||||
Text
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- RGB Sliders Section -->
|
||||
<div class="mb-4">
|
||||
<!-- Link sliders -->
|
||||
<div v-show="activeTab === 'link'" class="space-y-4">
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs text-$vp-c-text-2">
|
||||
<span>Red</span>
|
||||
<span>{{ linkR }}</span>
|
||||
</div>
|
||||
<input type="range" min="0" max="255" v-model.number="linkR" class="custom-slider w-full" style="--slider-color: #ff4d4d" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs text-$vp-c-text-2">
|
||||
<span>Green</span>
|
||||
<span>{{ linkG }}</span>
|
||||
</div>
|
||||
<input type="range" min="0" max="255" v-model.number="linkG" class="custom-slider w-full" style="--slider-color: #4dff4d" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs text-$vp-c-text-2">
|
||||
<span>Blue</span>
|
||||
<span>{{ linkB }}</span>
|
||||
</div>
|
||||
<input type="range" min="0" max="255" v-model.number="linkB" class="custom-slider w-full" style="--slider-color: #4d4dff" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Background sliders -->
|
||||
<div v-show="activeTab === 'bg'" class="space-y-4">
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs text-$vp-c-text-2">
|
||||
<span>Red</span>
|
||||
<span>{{ bgR }}</span>
|
||||
</div>
|
||||
<input type="range" min="0" max="255" v-model.number="bgR" class="custom-slider w-full" style="--slider-color: #ff4d4d" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs text-$vp-c-text-2">
|
||||
<span>Green</span>
|
||||
<span>{{ bgG }}</span>
|
||||
</div>
|
||||
<input type="range" min="0" max="255" v-model.number="bgG" class="custom-slider w-full" style="--slider-color: #4dff4d" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs text-$vp-c-text-2">
|
||||
<span>Blue</span>
|
||||
<span>{{ bgB }}</span>
|
||||
</div>
|
||||
<input type="range" min="0" max="255" v-model.number="bgB" class="custom-slider w-full" style="--slider-color: #4d4dff" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Text sliders -->
|
||||
<div v-show="activeTab === 'text'" class="space-y-4">
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs text-$vp-c-text-2">
|
||||
<span>Red</span>
|
||||
<span>{{ textR }}</span>
|
||||
</div>
|
||||
<input type="range" min="0" max="255" v-model.number="textR" class="custom-slider w-full" style="--slider-color: #ff4d4d" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs text-$vp-c-text-2">
|
||||
<span>Green</span>
|
||||
<span>{{ textG }}</span>
|
||||
</div>
|
||||
<input type="range" min="0" max="255" v-model.number="textG" class="custom-slider w-full" style="--slider-color: #4dff4d" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs text-$vp-c-text-2">
|
||||
<span>Blue</span>
|
||||
<span>{{ textB }}</span>
|
||||
</div>
|
||||
<input type="range" min="0" max="255" v-model.number="textB" class="custom-slider w-full" style="--slider-color: #4d4dff" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Input Fields (shown for active tab) -->
|
||||
<div class="mb-6">
|
||||
<!-- Link Inputs -->
|
||||
<div v-show="activeTab === 'link'" class="grid grid-cols-5 gap-2">
|
||||
<div class="col-span-2">
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">HEX</label>
|
||||
<input
|
||||
v-model="linkHex"
|
||||
type="text"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">R</label>
|
||||
<input v-model.number="linkR" type="number" min="0" max="255"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">G</label>
|
||||
<input v-model.number="linkG" type="number" min="0" max="255"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">B</label>
|
||||
<input v-model.number="linkB" type="number" min="0" max="255"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Background Inputs -->
|
||||
<div v-show="activeTab === 'bg'" class="grid grid-cols-5 gap-2">
|
||||
<div class="col-span-2">
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">HEX</label>
|
||||
<input
|
||||
v-model="bgHex"
|
||||
type="text"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">R</label>
|
||||
<input v-model.number="bgR" type="number" min="0" max="255"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">G</label>
|
||||
<input v-model.number="bgG" type="number" min="0" max="255"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">B</label>
|
||||
<input v-model.number="bgB" type="number" min="0" max="255"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Text Inputs -->
|
||||
<div v-show="activeTab === 'text'" class="grid grid-cols-5 gap-2">
|
||||
<div class="col-span-2">
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">HEX</label>
|
||||
<input
|
||||
v-model="textHex"
|
||||
type="text"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">R</label>
|
||||
<input v-model.number="textR" type="number" min="0" max="255"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">G</label>
|
||||
<input v-model.number="textG" type="number" min="0" max="255"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-$vp-c-text-2 text-xs block mb-1">B</label>
|
||||
<input v-model.number="textB" type="number" min="0" max="255"
|
||||
class="bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 w-full rounded border px-2 py-1.5 text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Warnings -->
|
||||
<div v-if="warnings.length" class="mb-4 rounded-lg bg-yellow-500/10 border border-yellow-500/20 p-3">
|
||||
<div v-for="(warn, i) in warnings" :key="i" class="flex items-center gap-2 text-yellow-500 text-xs font-medium">
|
||||
<div class="i-carbon-warning h-4 w-4 shrink-0" />
|
||||
<span>{{ warn }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
class="hover:bg-$vp-c-bg-alt border-$vp-c-divider text-$vp-c-text-1 flex-1 rounded-lg border px-4 py-2 font-medium transition-colors"
|
||||
@click="close"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 rounded-lg px-4 py-2 font-medium transition-colors"
|
||||
:style="{ backgroundColor: linkColor, color: textColor }"
|
||||
@click="apply"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-enter-active,
|
||||
.modal-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-enter-from,
|
||||
.modal-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-enter-active .bg-\$vp-c-bg,
|
||||
.modal-leave-active .bg-\$vp-c-bg {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-enter-from .bg-\$vp-c-bg,
|
||||
.modal-leave-to .bg-\$vp-c-bg {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
/* Custom slider styling */
|
||||
.custom-slider {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
transparent 0%,
|
||||
var(--slider-color, var(--vp-c-brand-1)) 100%
|
||||
);
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.custom-slider:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.custom-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--vp-c-brand-1);
|
||||
cursor: pointer;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.custom-slider::-moz-range-thumb {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--vp-c-brand-1);
|
||||
cursor: pointer;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,7 +3,7 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|||
import { useTheme } from '../themes/themeHandler'
|
||||
import type { DisplayMode } from '../themes/types'
|
||||
|
||||
const { mode, setMode, amoledEnabled, setAmoledEnabled } = useTheme()
|
||||
const { mode, setMode, state, amoledEnabled, setAmoledEnabled } = useTheme()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const dropdownRef = ref<HTMLElement | null>(null)
|
||||
|
|
@ -15,33 +15,18 @@ interface ModeChoice {
|
|||
isAmoled?: boolean
|
||||
}
|
||||
|
||||
const baseModeChoices: ModeChoice[] = [
|
||||
const modeChoices: ModeChoice[] = [
|
||||
{ mode: 'light', label: 'Light', icon: 'i-ph-sun-duotone' },
|
||||
{ mode: 'dark', label: 'Dark', icon: 'i-ph-moon-duotone' },
|
||||
{ mode: 'dark', label: 'AMOLED', icon: 'i-ph-moon-stars-duotone', isAmoled: true },
|
||||
{ mode: 'custom', label: 'Custom', icon: 'i-lucide-palette' }
|
||||
{ mode: 'dark', label: 'AMOLED', icon: 'i-ph-moon-stars-duotone', isAmoled: true }
|
||||
]
|
||||
|
||||
// Only show custom mode when already in custom mode
|
||||
const modeChoices = computed(() => {
|
||||
const current = (mode && (mode as any).value) ? (mode as any).value : 'light'
|
||||
if (current === 'custom') {
|
||||
return baseModeChoices
|
||||
}
|
||||
// Filter out custom mode when not in custom
|
||||
return baseModeChoices.filter(choice => choice.mode !== 'custom')
|
||||
})
|
||||
|
||||
const currentChoice = computed(() => {
|
||||
const current = (mode && (mode as any).value) ? (mode as any).value : 'light'
|
||||
// Handle custom mode
|
||||
if (current === 'custom') {
|
||||
return baseModeChoices[3] // Custom option
|
||||
}
|
||||
if (current === 'dark' && amoledEnabled.value) {
|
||||
return baseModeChoices[2] // AMOLED option
|
||||
return modeChoices[2] // AMOLED option
|
||||
}
|
||||
return baseModeChoices.find(choice => choice.mode === current && !choice.isAmoled) || baseModeChoices[0]
|
||||
return modeChoices.find(choice => choice.mode === current && !choice.isAmoled) || modeChoices[0]
|
||||
})
|
||||
|
||||
const toggleDropdown = () => {
|
||||
|
|
@ -49,20 +34,6 @@ const toggleDropdown = () => {
|
|||
}
|
||||
|
||||
const selectMode = (choice: ModeChoice) => {
|
||||
const current = (mode && (mode as any).value) ? (mode as any).value : 'light'
|
||||
|
||||
// Prevent switching to Light/Dark/AMOLED when in custom mode
|
||||
if (current === 'custom' && choice.mode !== 'custom') {
|
||||
isOpen.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent switching when clicking custom (clicking custom does nothing)
|
||||
if (choice.mode === 'custom') {
|
||||
isOpen.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (choice.isAmoled) {
|
||||
setMode('dark')
|
||||
setAmoledEnabled(true)
|
||||
|
|
@ -75,31 +46,12 @@ const selectMode = (choice: ModeChoice) => {
|
|||
|
||||
const isActiveChoice = (choice: ModeChoice) => {
|
||||
const current = (mode && (mode as any).value) ? (mode as any).value : 'light'
|
||||
// Handle custom mode
|
||||
if (choice.mode === 'custom') {
|
||||
return current === 'custom'
|
||||
}
|
||||
if (choice.isAmoled) {
|
||||
return current === 'dark' && amoledEnabled.value
|
||||
}
|
||||
return choice.mode === current && !choice.isAmoled && !amoledEnabled.value
|
||||
}
|
||||
|
||||
// Check if a choice should be disabled
|
||||
const isDisabled = (choice: ModeChoice) => {
|
||||
const current = (mode && (mode as any).value) ? (mode as any).value : 'light'
|
||||
// Disable Light/Dark/AMOLED when in custom mode
|
||||
return current === 'custom' && choice.mode !== 'custom'
|
||||
}
|
||||
|
||||
// Get tooltip for disabled items
|
||||
const getTooltip = (choice: ModeChoice) => {
|
||||
if (isDisabled(choice)) {
|
||||
return 'Use default themes to select this'
|
||||
}
|
||||
return choice.label
|
||||
}
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
|
||||
isOpen.value = false
|
||||
|
|
@ -134,8 +86,7 @@ onUnmounted(() => {
|
|||
v-for="(choice, index) in modeChoices"
|
||||
:key="index"
|
||||
class="theme-dropdown-item"
|
||||
:class="{ active: isActiveChoice(choice), disabled: isDisabled(choice) }"
|
||||
:title="getTooltip(choice)"
|
||||
:class="{ active: isActiveChoice(choice) }"
|
||||
@click="selectMode(choice)"
|
||||
>
|
||||
<div :class="[choice.icon, 'text-lg']" />
|
||||
|
|
@ -215,16 +166,6 @@ onUnmounted(() => {
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: var(--vp-c-text-3);
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,10 @@
|
|||
import { ref, onMounted, computed } from 'vue'
|
||||
import type { DisplayMode, ThemeState, Theme, ModeColors } from './types'
|
||||
import { themeRegistry } from './configs'
|
||||
import tinycolor from 'tinycolor2'
|
||||
|
||||
const STORAGE_KEY_THEME = 'vitepress-theme-name'
|
||||
const STORAGE_KEY_MODE = 'vitepress-display-mode'
|
||||
const STORAGE_KEY_AMOLED = 'vitepress-amoled-enabled'
|
||||
const STORAGE_KEY_PREVIOUS_MODE = 'vitepress-previous-mode'
|
||||
|
||||
export class ThemeHandler {
|
||||
private state = ref<ThemeState>({
|
||||
|
|
@ -31,178 +29,11 @@ export class ThemeHandler {
|
|||
theme: null
|
||||
})
|
||||
private amoledEnabled = ref(false)
|
||||
private previousMode = ref<DisplayMode>('light')
|
||||
|
||||
constructor() {
|
||||
this.initializeTheme()
|
||||
}
|
||||
|
||||
private registerCustomThemeFromStorage() {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
// Load saved custom colors from localStorage
|
||||
const savedLinkColor = localStorage.getItem('custom-theme-link-color') || '#ffffff'
|
||||
const savedTextColor = localStorage.getItem('custom-theme-text-color') || '#cccccc'
|
||||
const savedBgColor = localStorage.getItem('custom-theme-bg-color') || '#000000'
|
||||
|
||||
// Create lighter versions of background for cards
|
||||
// Increase lightening to make cards more distinct
|
||||
const lightenedBg = tinycolor(savedBgColor).lighten(10).toString()
|
||||
const lightenedBgAlt = tinycolor(savedBgColor).lighten(15).toString()
|
||||
|
||||
// Create custom theme with saved colors
|
||||
const customTheme = {
|
||||
name: 'custom',
|
||||
displayName: 'Custom',
|
||||
preview: savedBgColor,
|
||||
modes: {
|
||||
light: {
|
||||
brand: {
|
||||
1: savedLinkColor,
|
||||
2: savedLinkColor,
|
||||
3: savedLinkColor,
|
||||
soft: savedLinkColor
|
||||
},
|
||||
bg: savedBgColor,
|
||||
bgAlt: lightenedBg,
|
||||
bgElv: lightenedBgAlt,
|
||||
text: {
|
||||
1: savedTextColor,
|
||||
2: savedTextColor,
|
||||
3: savedTextColor
|
||||
},
|
||||
button: {
|
||||
brand: {
|
||||
bg: savedLinkColor,
|
||||
border: savedLinkColor,
|
||||
text: savedBgColor,
|
||||
hoverBorder: savedLinkColor,
|
||||
hoverText: savedBgColor,
|
||||
hoverBg: savedLinkColor,
|
||||
activeBorder: savedLinkColor,
|
||||
activeText: savedBgColor,
|
||||
activeBg: savedLinkColor
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: savedBgColor,
|
||||
border: savedLinkColor,
|
||||
text: savedTextColor,
|
||||
textDeep: savedTextColor
|
||||
},
|
||||
tip: {
|
||||
bg: '#D8F8E4',
|
||||
border: '#447A61',
|
||||
text: '#2D6A58',
|
||||
textDeep: '#166534'
|
||||
},
|
||||
warning: {
|
||||
bg: '#FCEFC3',
|
||||
border: '#9A8034',
|
||||
text: '#9C701B',
|
||||
textDeep: '#92400e'
|
||||
},
|
||||
danger: {
|
||||
bg: '#FBE1E2',
|
||||
border: '#B3565E',
|
||||
text: '#912239',
|
||||
textDeep: '#991b1b'
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
bg: savedLinkColor
|
||||
},
|
||||
home: {
|
||||
heroNameColor: savedLinkColor,
|
||||
heroNameBackground: savedBgColor,
|
||||
heroImageBackground: `linear-gradient(135deg, ${savedBgColor} 0%, ${savedLinkColor} 100%)`,
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
},
|
||||
dark: {
|
||||
brand: {
|
||||
1: savedLinkColor,
|
||||
2: savedLinkColor,
|
||||
3: savedLinkColor,
|
||||
soft: savedLinkColor
|
||||
},
|
||||
bg: savedBgColor,
|
||||
bgAlt: lightenedBg,
|
||||
bgElv: lightenedBgAlt,
|
||||
text: {
|
||||
1: savedTextColor,
|
||||
2: savedTextColor,
|
||||
3: savedTextColor
|
||||
},
|
||||
button: {
|
||||
brand: {
|
||||
bg: savedLinkColor,
|
||||
border: savedLinkColor,
|
||||
text: savedBgColor,
|
||||
hoverBorder: savedLinkColor,
|
||||
hoverText: savedBgColor,
|
||||
hoverBg: savedLinkColor,
|
||||
activeBorder: savedLinkColor,
|
||||
activeText: savedBgColor,
|
||||
activeBg: savedLinkColor
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: savedBgColor,
|
||||
border: savedLinkColor,
|
||||
text: savedTextColor,
|
||||
textDeep: savedTextColor
|
||||
},
|
||||
tip: {
|
||||
bg: '#0C2A20',
|
||||
border: '#184633',
|
||||
text: '#B0EBC9',
|
||||
textDeep: '#166534'
|
||||
},
|
||||
warning: {
|
||||
bg: '#403207',
|
||||
border: '#7E6211',
|
||||
text: '#F9DE88',
|
||||
textDeep: '#92400e'
|
||||
},
|
||||
danger: {
|
||||
bg: '#3F060A',
|
||||
border: '#7C0F18',
|
||||
text: '#F7C1BC',
|
||||
textDeep: '#991b1b'
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
bg: savedLinkColor
|
||||
},
|
||||
home: {
|
||||
heroNameColor: savedLinkColor,
|
||||
heroNameBackground: savedBgColor,
|
||||
heroImageBackground: `linear-gradient(135deg, ${savedBgColor} 0%, ${savedLinkColor} 100%)`,
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register custom theme
|
||||
themeRegistry['custom'] = customTheme
|
||||
}
|
||||
|
||||
private initializeTheme() {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
|
|
@ -211,11 +42,6 @@ export class ThemeHandler {
|
|||
const savedMode = localStorage.getItem(STORAGE_KEY_MODE) as DisplayMode | null
|
||||
const savedAmoled = localStorage.getItem(STORAGE_KEY_AMOLED) === 'true'
|
||||
|
||||
// If custom theme was saved, register it early from localStorage
|
||||
if (savedTheme === 'custom') {
|
||||
this.registerCustomThemeFromStorage()
|
||||
}
|
||||
|
||||
if (themeRegistry[savedTheme]) {
|
||||
this.state.value.currentTheme = savedTheme
|
||||
this.state.value.theme = themeRegistry[savedTheme]
|
||||
|
|
@ -265,9 +91,7 @@ export class ThemeHandler {
|
|||
|
||||
if (!theme) return
|
||||
|
||||
// Custom mode uses dark mode colors from the theme
|
||||
const effectiveMode = currentMode === 'custom' ? 'dark' : currentMode
|
||||
const modeColors = theme.modes[effectiveMode]
|
||||
const modeColors = theme.modes[currentMode]
|
||||
|
||||
this.applyDOMClasses(currentMode)
|
||||
this.applyCSSVariables(modeColors, theme)
|
||||
|
|
@ -283,7 +107,7 @@ export class ThemeHandler {
|
|||
const root = document.documentElement
|
||||
|
||||
// Remove all mode classes
|
||||
root.classList.remove('dark', 'light', 'amoled', 'custom')
|
||||
root.classList.remove('dark', 'light', 'amoled')
|
||||
|
||||
// Add current mode class
|
||||
root.classList.add(mode)
|
||||
|
|
@ -335,14 +159,6 @@ export class ThemeHandler {
|
|||
root.style.setProperty('--vp-c-bg', bgColor)
|
||||
root.style.setProperty('--vp-c-bg-alt', bgAltColor)
|
||||
root.style.setProperty('--vp-c-bg-elv', bgElvColor)
|
||||
|
||||
// Apply additional background variables for cards and other elements
|
||||
root.style.setProperty('--vp-c-bg-soft', bgAltColor)
|
||||
root.style.setProperty('--vp-c-default-soft', bgElvColor)
|
||||
root.style.setProperty('--vp-c-default-1', bgAltColor)
|
||||
root.style.setProperty('--vp-c-default-2', bgElvColor)
|
||||
root.style.setProperty('--vp-c-default-3', bgColor)
|
||||
|
||||
if (colors.bgMark) {
|
||||
root.style.setProperty('--vp-c-bg-mark', colors.bgMark)
|
||||
}
|
||||
|
|
@ -466,12 +282,6 @@ export class ThemeHandler {
|
|||
}
|
||||
|
||||
public setMode(mode: DisplayMode) {
|
||||
// Save current mode as previous mode before switching to custom
|
||||
if (mode === 'custom' && this.state.value.currentMode !== 'custom') {
|
||||
this.previousMode.value = this.state.value.currentMode
|
||||
localStorage.setItem(STORAGE_KEY_PREVIOUS_MODE, this.previousMode.value)
|
||||
}
|
||||
|
||||
this.state.value.currentMode = mode
|
||||
localStorage.setItem(STORAGE_KEY_MODE, mode)
|
||||
this.applyTheme()
|
||||
|
|
@ -509,9 +319,7 @@ export class ThemeHandler {
|
|||
if (!theme) return
|
||||
// If theme doesn't specify brand colors, force ColorPicker to reapply its selection
|
||||
const currentMode = this.state.value.currentMode
|
||||
// Custom mode uses dark mode colors
|
||||
const effectiveMode = currentMode === 'custom' ? 'dark' : currentMode
|
||||
const modeColors = theme.modes[effectiveMode]
|
||||
const modeColors = theme.modes[currentMode]
|
||||
|
||||
if (!modeColors.brand || !modeColors.brand[1]) {
|
||||
// Trigger a custom event that ColorPicker can listen to
|
||||
|
|
@ -552,15 +360,6 @@ export class ThemeHandler {
|
|||
public isAmoledMode() {
|
||||
return this.state.value.currentMode === 'dark' && this.amoledEnabled.value
|
||||
}
|
||||
|
||||
public restorePreviousMode() {
|
||||
// Only restore if currently in custom mode
|
||||
if (this.state.value.currentMode === 'custom') {
|
||||
const savedPreviousMode = localStorage.getItem(STORAGE_KEY_PREVIOUS_MODE) as DisplayMode | null
|
||||
const modeToRestore = savedPreviousMode || this.previousMode.value
|
||||
this.setMode(modeToRestore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global theme handler instance
|
||||
|
|
@ -596,7 +395,6 @@ export function useTheme() {
|
|||
amoledEnabled: handler.getAmoledEnabledRef(),
|
||||
setAmoledEnabled: (enabled: boolean) => handler.setAmoledEnabled(enabled),
|
||||
toggleAmoled: () => handler.toggleAmoled(),
|
||||
restorePreviousMode: () => handler.restorePreviousMode(),
|
||||
state
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type DisplayMode = 'light' | 'dark' | 'custom'
|
||||
export type DisplayMode = 'light' | 'dark'
|
||||
|
||||
export interface ModeColors {
|
||||
// Brand colors (optional - if not specified, ColorPicker values are used)
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export function getHeader(id: string) {
|
|||
const title =
|
||||
'<div class="space-y-2 not-prose"><h1 class="text-4xl font-extrabold tracking-tight text-primary underline lg:text-5xl lg:leading-[3.5rem]">'
|
||||
|
||||
const description = '<p style="color: var(--vp-c-text-1)">'
|
||||
const description = '<p class="text-black dark:text-text-2">'
|
||||
|
||||
const feedback = meta.build.api ? '<Feedback />' : ''
|
||||
|
||||
|
|
|
|||
BIN
package-lock.json
generated
BIN
package-lock.json
generated
Binary file not shown.
|
|
@ -36,9 +36,9 @@
|
|||
"nprogress": "^0.2.0",
|
||||
"pathe": "^2.0.3",
|
||||
"reka-ui": "^2.6.1",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"unocss": "66.5.10",
|
||||
"vitepress": "^1.6.4",
|
||||
"vue": "^3.5.25",
|
||||
"x-satori": "^0.4.0",
|
||||
"zod": "^4.1.13"
|
||||
},
|
||||
|
|
@ -61,7 +61,6 @@
|
|||
"@iconify/utils": "^3.1.0",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
"@vue/compiler-sfc": "^3.5.27",
|
||||
"floating-vue": "^5.2.2",
|
||||
"nitro-cloudflare-dev": "^0.2.2",
|
||||
|
|
@ -74,7 +73,6 @@
|
|||
"vite-plugin-optimize-exclude": "^0.0.1",
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"vite-plugin-terminal": "^1.3.0",
|
||||
"vue": "^3.5.0",
|
||||
"wrangler": "^4.52.1"
|
||||
},
|
||||
"pnpm": {
|
||||
|
|
|
|||
BIN
pnpm-lock.yaml
BIN
pnpm-lock.yaml
Binary file not shown.
|
|
@ -102,12 +102,12 @@ export default defineConfig({
|
|||
{
|
||||
display: 'inline-block',
|
||||
padding: '0.2em 0.4em',
|
||||
'font-size': '0.75em',
|
||||
'font-weight': '500',
|
||||
'line-height': '1',
|
||||
fontSize: '0.75em',
|
||||
fontWeight: '500',
|
||||
lineHeight: '1',
|
||||
color: 'var(--vp-c-text-1)',
|
||||
'background-color': 'rgb(var(--vp-c-bg-alt))',
|
||||
'border-radius': '4px'
|
||||
backgroundColor: 'rgb(var(--vp-c-bg-alt))',
|
||||
borderRadius: '4px'
|
||||
}
|
||||
]
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in a new issue