mirror of
https://github.com/fmhy/edit.git
synced 2026-01-12 23:11:06 +11:00
Merge branch 'fmhy:main' into main
This commit is contained in:
commit
c9e9ee0236
42 changed files with 5003 additions and 3687 deletions
|
|
@ -96,7 +96,7 @@ export default defineConfig({
|
|||
{
|
||||
find: /^.*VPSwitchAppearance\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/Appearance.vue', import.meta.url)
|
||||
new URL('./theme/components/ThemeDropdown.vue', import.meta.url)
|
||||
)
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import Announcement from './components/Announcement.vue'
|
||||
import Sidebar from './components/SidebarCard.vue'
|
||||
import { useTheme } from './themes/themeHandler'
|
||||
|
||||
const { isDark } = useData()
|
||||
const { setMode } = useTheme()
|
||||
|
||||
const enableTransitions = () =>
|
||||
'startViewTransition' in document &&
|
||||
|
|
@ -12,6 +15,8 @@ const enableTransitions = () =>
|
|||
provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
|
||||
if (!enableTransitions()) {
|
||||
isDark.value = !isDark.value
|
||||
// Sync with theme handler
|
||||
setMode(isDark.value ? 'dark' : 'light')
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -26,6 +31,8 @@ provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
|
|||
// @ts-expect-error
|
||||
await document.startViewTransition(async () => {
|
||||
isDark.value = !isDark.value
|
||||
// Sync with theme handler
|
||||
setMode(isDark.value ? 'dark' : 'light')
|
||||
await nextTick()
|
||||
}).ready
|
||||
|
||||
|
|
|
|||
|
|
@ -1,141 +1,262 @@
|
|||
<script setup lang="ts">
|
||||
import { colors } from '@fmhy/colors'
|
||||
import { useStorage, useStyleTag } from '@vueuse/core'
|
||||
import { watch, onMounted } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { watch, onMounted, nextTick } from 'vue'
|
||||
import { useTheme } from '../themes/themeHandler'
|
||||
import { themeRegistry } from '../themes/configs'
|
||||
import type { Theme } from '../themes/types'
|
||||
import Switch from './Switch.vue'
|
||||
|
||||
const colorScales = [
|
||||
'50',
|
||||
'100',
|
||||
'200',
|
||||
'300',
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
'800',
|
||||
'900',
|
||||
'950'
|
||||
] as const
|
||||
|
||||
type ColorNames = keyof typeof colors
|
||||
const selectedColor = useStorage<ColorNames>('preferred-color', 'swarm')
|
||||
const isAmoledMode = useStorage('amoled-mode', false)
|
||||
|
||||
// Use the theme system
|
||||
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>
|
||||
|
||||
const { css } = useStyleTag('', { id: 'brand-color' })
|
||||
// Preset themes (exclude dynamically generated color- themes)
|
||||
const presetThemeNames = Object.keys(themeRegistry).filter((k) => !k.startsWith('color-'))
|
||||
|
||||
const updateThemeColor = (colorName: ColorNames, amoledEnabled: boolean) => {
|
||||
const getThemePreviewStyle = (name: string) => {
|
||||
const theme = themeRegistry[name]
|
||||
if (!theme) return {}
|
||||
const modeKey = (mode && (mode as any).value) ? (mode as any).value as keyof typeof theme.modes : 'light'
|
||||
const modeColors = theme.modes[modeKey]
|
||||
|
||||
if (theme.preview) {
|
||||
// If preview is a URL or gradient, use it directly
|
||||
if (theme.preview.startsWith('http') || theme.preview.startsWith('data:')) {
|
||||
return { backgroundImage: `url(${theme.preview})`, backgroundSize: 'cover' }
|
||||
}
|
||||
return { background: theme.preview }
|
||||
}
|
||||
|
||||
if (modeColors?.brand && modeColors.brand[1] && modeColors.brand[2]) {
|
||||
return {
|
||||
background: `linear-gradient(135deg, ${modeColors.brand[1]} 0%, ${modeColors.brand[2]} 100%)`
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to CSS var brand if present
|
||||
return { background: 'var(--vp-c-brand-1)' }
|
||||
}
|
||||
|
||||
const generateThemeFromColor = (colorName: ColorNames): Theme => {
|
||||
const colorSet = colors[colorName]
|
||||
|
||||
const cssVars = colorScales
|
||||
.map((scale) => `--vp-c-brand-${scale}: ${colorSet[scale]};`)
|
||||
.join('\n ')
|
||||
|
||||
const htmlElement = document.documentElement
|
||||
|
||||
if (amoledEnabled) {
|
||||
htmlElement.classList.add('theme-amoled')
|
||||
} else {
|
||||
htmlElement.classList.remove('theme-amoled')
|
||||
return {
|
||||
name: `color-${colorName}`,
|
||||
displayName: normalizeColorName(colorName),
|
||||
modes: {
|
||||
light: {
|
||||
brand: {
|
||||
1: colorSet[500],
|
||||
2: colorSet[600],
|
||||
3: colorSet[800],
|
||||
soft: colorSet[400]
|
||||
},
|
||||
bg: '#f8fafc',
|
||||
bgAlt: '#eef2f5',
|
||||
bgElv: 'rgba(255, 255, 255, 0.8)',
|
||||
bgMark: 'rgb(226, 232, 240)',
|
||||
text: {
|
||||
1: '#0f172a',
|
||||
2: '#334155',
|
||||
3: '#64748b'
|
||||
},
|
||||
button: {
|
||||
brand: {
|
||||
bg: colorSet[500],
|
||||
border: colorSet[400],
|
||||
text: 'rgba(255, 255, 255)',
|
||||
hoverBorder: colorSet[400],
|
||||
hoverText: 'rgba(255, 255, 255)',
|
||||
hoverBg: colorSet[400],
|
||||
activeBorder: colorSet[400],
|
||||
activeText: 'rgba(255, 255, 255)',
|
||||
activeBg: colorSet[500]
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
|
||||
const darkBg = amoledEnabled ? '#000000' : 'rgb(26, 26, 26)'
|
||||
const darkBgAlt = amoledEnabled ? '#000000' : 'rgb(23, 23, 23)'
|
||||
const darkBgElv = amoledEnabled ? 'rgba(0, 0, 0, 0.9)' : 'rgba(23, 23, 23, 0.8)'
|
||||
const darkBgSoft = amoledEnabled ? '#000000' : 'rgb(23, 23, 23)'
|
||||
|
||||
css.value = `
|
||||
:root {
|
||||
${cssVars}
|
||||
--vp-c-brand-1: ${colorSet[500]};
|
||||
--vp-c-brand-2: ${colorSet[600]};
|
||||
--vp-c-brand-3: ${colorSet[800]};
|
||||
--vp-c-brand-soft: ${colorSet[400]};
|
||||
--vp-c-bg: #ffffff !important;
|
||||
--vp-c-bg-alt: #f9f9f9 !important;
|
||||
--vp-c-bg-elv: rgba(255, 255, 255, 0.7) !important;
|
||||
--vp-c-bg-soft: #f9f9f9 !important;
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: `${colorSet[100]}`,
|
||||
border: `${colorSet[800]}`,
|
||||
text: `${colorSet[800]}`,
|
||||
textDeep: `${colorSet[900]}`
|
||||
},
|
||||
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'
|
||||
}
|
||||
|
||||
.dark {
|
||||
${cssVars}
|
||||
--vp-c-brand-1: ${colorSet[400]};
|
||||
--vp-c-brand-2: ${colorSet[500]};
|
||||
--vp-c-brand-3: ${colorSet[700]};
|
||||
--vp-c-brand-soft: ${colorSet[300]};
|
||||
--vp-c-bg: ${darkBg} !important;
|
||||
--vp-c-bg-alt: ${darkBgAlt} !important;
|
||||
--vp-c-bg-elv: ${darkBgElv} !important;
|
||||
--vp-c-bg-soft: ${darkBgSoft} !important;
|
||||
},
|
||||
selection: {
|
||||
bg: colorSet[200]
|
||||
},
|
||||
home: {
|
||||
heroNameColor: 'transparent',
|
||||
heroNameBackground: `-webkit-linear-gradient(120deg, ${colorSet[400]} 30%, ${colorSet[500]})`,
|
||||
heroImageBackground: `linear-gradient(-45deg, ${colorSet[400]} 50%, ${colorSet[500]} 50%)`,
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
|
||||
html, body {
|
||||
background-color: #ffffff !important;
|
||||
},
|
||||
dark: {
|
||||
brand: {
|
||||
1: colorSet[400],
|
||||
2: colorSet[500],
|
||||
3: colorSet[600],
|
||||
soft: colorSet[300]
|
||||
},
|
||||
bg: '#1A1A1A',
|
||||
bgAlt: '#171717',
|
||||
bgElv: '#1a1a1acc',
|
||||
button: {
|
||||
brand: {
|
||||
bg: colorSet[400],
|
||||
border: colorSet[300],
|
||||
text: 'rgba(15, 23, 42)',
|
||||
hoverBorder: colorSet[300],
|
||||
hoverText: 'rgba(15, 23, 42)',
|
||||
hoverBg: colorSet[300],
|
||||
activeBorder: colorSet[300],
|
||||
activeText: 'rgba(15, 23, 42)',
|
||||
activeBg: colorSet[400]
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
|
||||
.VPApp, .Layout, .VPContent, .VPHome, .VPHero, #app, .vp-doc {
|
||||
background-color: #ffffff !important;
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: `${colorSet[950]}`,
|
||||
border: `${colorSet[700]}`,
|
||||
text: `${colorSet[200]}`,
|
||||
textDeep: `${colorSet[200]}`
|
||||
},
|
||||
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: colorSet[800]
|
||||
},
|
||||
home: {
|
||||
heroNameColor: 'transparent',
|
||||
heroNameBackground: `-webkit-linear-gradient(120deg, ${colorSet[400]} 30%, ${colorSet[500]})`,
|
||||
heroImageBackground: `linear-gradient(-45deg, ${colorSet[400]} 50%, ${colorSet[500]} 50%)`,
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
|
||||
.dark html, .dark body {
|
||||
background-color: ${darkBg} !important;
|
||||
}
|
||||
|
||||
.dark .VPApp, .dark .Layout, .dark .VPContent, .dark .VPHome, .dark .VPHero, .dark #app, .dark .vp-doc {
|
||||
background-color: ${darkBg} !important;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (isAmoledMode.value) {
|
||||
document.documentElement.classList.add('theme-amoled')
|
||||
}
|
||||
|
||||
// Re-apply the theme to ensure everything is initialized
|
||||
updateThemeColor(selectedColor.value, isAmoledMode.value)
|
||||
})
|
||||
|
||||
watch([selectedColor, isAmoledMode], ([color, amoled]) => {
|
||||
updateThemeColor(color, amoled)
|
||||
})
|
||||
|
||||
const normalizeColorName = (colorName: string) =>
|
||||
colorName.replaceAll(/-/g, ' ').charAt(0).toUpperCase() +
|
||||
colorName.slice(1).replaceAll(/-/g, ' ')
|
||||
|
||||
onMounted(async () => {
|
||||
// Don't auto-apply color theme - only apply when user explicitly selects
|
||||
// Wait for next tick to ensure theme handler is fully initialized
|
||||
await nextTick()
|
||||
})
|
||||
|
||||
watch(selectedColor, async (color) => {
|
||||
const theme = generateThemeFromColor(color)
|
||||
themeRegistry[`color-${color}`] = theme
|
||||
// Explicitly set the theme to override any previous selection
|
||||
await nextTick()
|
||||
console.log('Setting theme to:', `color-${color}`)
|
||||
console.log('Current themeName:', themeName ? themeName.value : undefined, 'mode:', mode ? (mode as any).value : undefined)
|
||||
setTheme(`color-${color}`)
|
||||
console.log('After setTheme, themeName:', themeName ? themeName.value : undefined)
|
||||
})
|
||||
|
||||
const toggleAmoled = () => {
|
||||
setAmoledEnabled(!amoledEnabled.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<!-- Color picker generated themes (render first) -->
|
||||
<div v-for="color in colorOptions" :key="color">
|
||||
<button
|
||||
:class="[
|
||||
'inline-block w-6 h-6 rounded-full transition-all duration-200'
|
||||
'inline-block w-6 h-6 rounded-full transition-all duration-200 border-2',
|
||||
selectedColor === color
|
||||
? 'border-slate-200 dark:border-slate-400 shadow-lg'
|
||||
: 'border-transparent'
|
||||
]"
|
||||
@click="selectedColor = color"
|
||||
:title="normalizeColorName(color)"
|
||||
>
|
||||
<span
|
||||
class="inline-block w-6 h-6 rounded-full"
|
||||
:style="{ backgroundColor: colors[color][500] }"
|
||||
class="inline-block w-full h-full rounded-full"
|
||||
:style="{ backgroundColor: colors[color][500], backgroundSize: 'cover', backgroundPosition: 'center', backgroundRepeat: 'no-repeat' }"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Preset themes (render at the end) -->
|
||||
<div v-for="t in presetThemeNames" :key="t">
|
||||
<button
|
||||
:class="[
|
||||
'inline-block w-6 h-6 rounded-full transition-all duration-200 border-2',
|
||||
(themeName && themeName.value === t)
|
||||
? 'border-slate-200 dark:border-slate-400 shadow-lg'
|
||||
: 'border-transparent'
|
||||
]"
|
||||
@click="setTheme(t)"
|
||||
:title="themeRegistry[t].displayName"
|
||||
>
|
||||
<span
|
||||
class="inline-block w-full h-full rounded-full"
|
||||
:style="Object.assign({ backgroundSize: 'cover', backgroundPosition: 'center', backgroundRepeat: 'no-repeat' }, getThemePreviewStyle(t))"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-sm text-$vp-c-text-2">
|
||||
Selected: {{ normalizeColorName(selectedColor) }}
|
||||
</div>
|
||||
|
||||
<!-- AMOLED toggle -->
|
||||
<div class="mt-4 flex items-center gap-2">
|
||||
<span class="text-sm text-$vp-c-text-2">AMOLED</span>
|
||||
<Switch v-model="isAmoledMode" @click="isAmoledMode = !isAmoledMode" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -145,36 +145,28 @@ const toggleCard = () => (isCardShown.value = !isCardShown.value)
|
|||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
class="mt-2 p-4 border-2 border-solid bg-brand-50 border-brand-300 dark:bg-brand-950 dark:border-brand-800 rounded-xl col-span-3 transition-colors duration-250"
|
||||
class="mt-2 p-4 border-2 border-solid bg-$vp-c-bg-alt border-$vp-c-divider rounded-xl col-span-3 transition-colors duration-250"
|
||||
>
|
||||
<div class="flex items-start md:items-center gap-3">
|
||||
<div class="pt-1 md:pt-0">
|
||||
<div
|
||||
class="w-10 h-10 rounded-full flex items-center justify-center bg-brand-500 dark:bg-brand-400"
|
||||
>
|
||||
<div class="w-10 h-10 rounded-full flex items-center justify-center bg-$vp-c-brand-3">
|
||||
<span
|
||||
:class="
|
||||
isCardShown === false
|
||||
? `i-lucide:mail w-6 h-6 text-white dark:text-brand-950`
|
||||
: `i-lucide:mail-x w-6 h-6 text-white dark:text-brand-950`
|
||||
? `i-lucide:mail w-6 h-6 text-$vp-c-text-1`
|
||||
: `i-lucide:mail-x w-6 h-6 text-$vp-c-text-1`
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex-grow flex items-start md:items-center gap-3 flex-col md:flex-row"
|
||||
>
|
||||
<div class="flex-grow flex items-start md:items-center gap-3 flex-col md:flex-row">
|
||||
<div class="flex-grow">
|
||||
<div class="font-semibold text-brand-950 dark:text-brand-50">
|
||||
Got feedback?
|
||||
</div>
|
||||
<div class="text-sm text-brand-800 dark:text-brand-100">
|
||||
We'd love to know what you think about this page.
|
||||
</div>
|
||||
<div class="font-semibold text-$vp-c-text-1">Got feedback?</div>
|
||||
<div class="text-sm text-$vp-c-text-2">We'd love to know what you think about this page.</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="inline-block text-center rounded-full px-4 py-2.5 text-sm font-medium border-2 border-solid text-brand-700 border-brand-300 dark:text-brand-100 dark:border-brand-800"
|
||||
class="inline-block text-center rounded-full px-4 py-2.5 text-sm font-medium border-2 border-solid text-$vp-c-text-1 border-$vp-c-divider"
|
||||
@click="toggleCard()"
|
||||
>
|
||||
Share Feedback
|
||||
|
|
@ -199,7 +191,7 @@ const toggleCard = () => (isCardShown.value = !isCardShown.value)
|
|||
<button
|
||||
v-for="item in feedbackOptions"
|
||||
:key="item.value"
|
||||
class="bg-bg border-$vp-c-default-soft hover:border-primary mt-2 select-none rounded border-2 border-solid font-bold transition-all duration-250 rounded-lg text-[14px] font-500 leading-normal m-0 px-3 py-1.5 text-center align-middle whitespace-nowrap"
|
||||
class="bg-$vp-c-bg border-$vp-c-default-soft hover:border-primary mt-2 select-none rounded border-2 border-solid font-bold transition-all duration-250 rounded-lg text-[14px] font-500 leading-normal m-0 px-3 py-1.5 text-center align-middle whitespace-nowrap"
|
||||
@click="handleSubmit(item.value)"
|
||||
>
|
||||
<span>{{ item.label }}</span>
|
||||
|
|
@ -240,9 +232,10 @@ const toggleCard = () => (isCardShown.value = !isCardShown.value)
|
|||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="border border-div rounded-lg transition-colors duration-250 inline-block text-14px font-500 leading-1.5 px-3 py-3 text-center align-middle whitespace-nowrap disabled:opacity-50 text-text-2 bg-brand-100 dark:bg-brand-700 border-brand-800 dark:border-brand-700 disabled:bg-brand-100 dark:disabled:bg-brand-900 hover:border-brand-900 dark:hover:border-brand-800 hover:bg-brand-200 dark:hover:bg-brand-800"
|
||||
class="btn btn-primary"
|
||||
:disabled="isDisabled"
|
||||
@click="handleSubmit()"
|
||||
:style="isDisabled ? {} : { 'background-color': 'var(--vp-button-brand-bg)', 'border-color': 'var(--vp-button-brand-border)', color: 'var(--vp-button-brand-text)' }"
|
||||
>
|
||||
Send Feedback 📩
|
||||
</button>
|
||||
|
|
@ -284,14 +277,14 @@ const toggleCard = () => (isCardShown.value = !isCardShown.value)
|
|||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-button-brand-text);
|
||||
background-color: var(--vp-button-brand-bg);
|
||||
border-color: var(--vp-button-brand-border);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--vp-c-brand-darker);
|
||||
border-color: var(--vp-c-brand-darker);
|
||||
background-color: var(--vp-button-brand-hover-bg);
|
||||
border-color: var(--vp-button-brand-hover-border);
|
||||
}
|
||||
|
||||
.heading {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import Field from './CardField.vue'
|
||||
import ColorPicker from './ColorPicker.vue'
|
||||
import ThemeSelector from './ThemeSelector.vue'
|
||||
import InputField from './InputField.vue'
|
||||
import ToggleStarred from './ToggleStarred.vue'
|
||||
</script>
|
||||
|
|
@ -26,6 +27,12 @@ import ToggleStarred from './ToggleStarred.vue'
|
|||
</template>
|
||||
</InputField>
|
||||
|
||||
<div class="mt-4">
|
||||
<ColorPicker />
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-$vp-c-divider">
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
184
docs/.vitepress/theme/components/ThemeDropdown.vue
Normal file
184
docs/.vitepress/theme/components/ThemeDropdown.vue
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useTheme } from '../themes/themeHandler'
|
||||
import type { DisplayMode } from '../themes/types'
|
||||
|
||||
const { mode, setMode, state, amoledEnabled, setAmoledEnabled } = useTheme()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const dropdownRef = ref<HTMLElement | null>(null)
|
||||
|
||||
interface ModeChoice {
|
||||
mode: DisplayMode
|
||||
label: string
|
||||
icon: string
|
||||
isAmoled?: boolean
|
||||
}
|
||||
|
||||
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 }
|
||||
]
|
||||
|
||||
const currentChoice = computed(() => {
|
||||
const current = (mode && (mode as any).value) ? (mode as any).value : 'light'
|
||||
if (current === 'dark' && amoledEnabled.value) {
|
||||
return modeChoices[2] // AMOLED option
|
||||
}
|
||||
return modeChoices.find(choice => choice.mode === current && !choice.isAmoled) || modeChoices[0]
|
||||
})
|
||||
|
||||
const toggleDropdown = () => {
|
||||
isOpen.value = !isOpen.value
|
||||
}
|
||||
|
||||
const selectMode = (choice: ModeChoice) => {
|
||||
if (choice.isAmoled) {
|
||||
setMode('dark')
|
||||
setAmoledEnabled(true)
|
||||
} else {
|
||||
setMode(choice.mode)
|
||||
setAmoledEnabled(false)
|
||||
}
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
const isActiveChoice = (choice: ModeChoice) => {
|
||||
const current = (mode && (mode as any).value) ? (mode as any).value : 'light'
|
||||
if (choice.isAmoled) {
|
||||
return current === 'dark' && amoledEnabled.value
|
||||
}
|
||||
return choice.mode === current && !choice.isAmoled && !amoledEnabled.value
|
||||
}
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
|
||||
isOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="dropdownRef" class="theme-dropdown">
|
||||
<button
|
||||
type="button"
|
||||
class="theme-dropdown-toggle"
|
||||
:title="currentChoice.label"
|
||||
@click="toggleDropdown"
|
||||
>
|
||||
<ClientOnly>
|
||||
<div :class="[currentChoice.icon, 'text-xl']" />
|
||||
</ClientOnly>
|
||||
</button>
|
||||
|
||||
<Transition name="dropdown">
|
||||
<div v-if="isOpen" class="theme-dropdown-menu">
|
||||
<button
|
||||
v-for="(choice, index) in modeChoices"
|
||||
:key="index"
|
||||
class="theme-dropdown-item"
|
||||
:class="{ active: isActiveChoice(choice) }"
|
||||
@click="selectMode(choice)"
|
||||
>
|
||||
<div :class="[choice.icon, 'text-lg']" />
|
||||
<span>{{ choice.label }}</span>
|
||||
<div v-if="isActiveChoice(choice)" class="i-ph-check text-lg ml-auto" />
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.theme-dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.theme-dropdown-toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
color: var(--vp-c-text-2);
|
||||
transition: color 0.5s;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
color: var(--vp-c-text-1);
|
||||
background: var(--vp-c-bg-elv);
|
||||
transition: color 0.25s, background 0.25s;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-dropdown-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: 0;
|
||||
min-width: 180px;
|
||||
background: var(--vp-c-bg-elv);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
padding: 6px;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(12px);
|
||||
|
||||
.dark & {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: var(--vp-c-text-1);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-enter-active,
|
||||
.dropdown-leave-active {
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
}
|
||||
|
||||
.dropdown-enter-from,
|
||||
.dropdown-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
</style>
|
||||
48
docs/.vitepress/theme/components/ThemeSelector.vue
Normal file
48
docs/.vitepress/theme/components/ThemeSelector.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useTheme } from '../themes/themeHandler'
|
||||
import { themeRegistry } from '../themes/configs'
|
||||
|
||||
const { themeName, setTheme, getAvailableThemes, state, mode } = useTheme()
|
||||
|
||||
const availableThemes = computed(() => getAvailableThemes())
|
||||
|
||||
const getThemePreview = (name: string) => {
|
||||
const theme = themeRegistry[name]
|
||||
if (theme?.preview) {
|
||||
return theme.preview
|
||||
}
|
||||
// Fallback: create gradient from theme's brand colors if they exist
|
||||
const modeKey = (mode && (mode as any).value) ? (mode as any).value : 'light'
|
||||
const colors = modeKey === 'dark' ? theme?.modes.dark : theme?.modes.light
|
||||
|
||||
if (colors?.brand && colors.brand[1] && colors.brand[2] && colors.brand[3]) {
|
||||
return `linear-gradient(135deg, ${colors.brand[1]} 0%, ${colors.brand[2]} 50%, ${colors.brand[3]} 100%)`
|
||||
}
|
||||
|
||||
return 'linear-gradient(135deg, var(--vp-c-brand-1) 0%, var(--vp-c-brand-2) 100%)'
|
||||
}
|
||||
|
||||
const normalizeThemeName = (name: string) =>
|
||||
name.replaceAll(/-/g, ' ').charAt(0).toUpperCase() +
|
||||
name.slice(1).replaceAll(/-/g, ' ')
|
||||
|
||||
const currentDisplayName = computed(() => {
|
||||
const t = themeName && (themeName as any).value ? (themeName as any).value : ''
|
||||
if (!t) return 'Default'
|
||||
const cfg = themeRegistry[t]
|
||||
if (cfg && cfg.displayName) return cfg.displayName
|
||||
// fallback: humanize the key
|
||||
return normalizeThemeName(t)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-2 text-sm text-$vp-c-text-1">Theme</div>
|
||||
<div class="text-sm text-$vp-c-text-2">
|
||||
<span class="font-medium">Theme:</span>
|
||||
<span class="ml-1">{{ currentDisplayName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -18,6 +18,7 @@ import type { Theme } from 'vitepress'
|
|||
import Components from '@fmhy/components'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import { loadProgress } from './composables/nprogress'
|
||||
import { useThemeHandler } from './themes/themeHandler'
|
||||
import Layout from './Layout.vue'
|
||||
import Post from './PostLayout.vue'
|
||||
|
||||
|
|
@ -34,5 +35,7 @@ export default {
|
|||
app.component('Post', Post)
|
||||
app.component('Feedback', Feedback)
|
||||
loadProgress(router)
|
||||
// Initialize theme handler
|
||||
useThemeHandler()
|
||||
}
|
||||
} satisfies Theme
|
||||
|
|
|
|||
109
docs/.vitepress/theme/themes/README.md
Normal file
109
docs/.vitepress/theme/themes/README.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# Theme System
|
||||
|
||||
This document explains the updated theme architecture, display modes, and integration components in the site.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Display modes: `light` and `dark`.
|
||||
- AMOLED: an enhancement of `dark` mode (pure black backgrounds) toggled on top of dark — not a separate mode.
|
||||
- Themes: color schemes and optional design tokens that apply across modes.
|
||||
- Modes are independent from themes; themes define colors and tokens for light/dark.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
docs/.vitepress/theme/themes/
|
||||
├── types.ts // Type definitions
|
||||
├── themeHandler.ts // Theme handler logic & DOM/CSS application
|
||||
├── index.ts // Exports
|
||||
└── configs/
|
||||
├── index.ts // Theme registry (default + named themes)
|
||||
└── catppuccin.ts // Example theme (default)
|
||||
```
|
||||
|
||||
## Core Types
|
||||
|
||||
- `DisplayMode`: `'light' | 'dark'`.
|
||||
- `Theme`: `{ name, displayName, preview?, logo?, modes: { light, dark }, ... }`.
|
||||
- `ModeColors`:
|
||||
- `brand?`: optional brand colors (`1`, `2`, `3`, `soft`). If omitted, the ColorPicker controls brand.
|
||||
- `bg`, `bgAlt`, `bgElv`, `bgMark?`.
|
||||
- `text?`: optional (`1`, `2`, `3`). If omitted, VitePress defaults are used.
|
||||
- `button`: `brand` and `alt` sub-objects with `bg`, `border`, `text`, `hover*`, `active*`.
|
||||
- `customBlock`: `info`, `tip`, `warning`, `danger` with `bg`, `border`, `text`, `textDeep`.
|
||||
- `selection`: `{ bg }`.
|
||||
- `home?`: optional hero styles.
|
||||
|
||||
## Handler Behavior (`themeHandler.ts`)
|
||||
|
||||
- Persists `theme` (`vitepress-theme-name`) and `mode` (`vitepress-display-mode`).
|
||||
- Applies HTML classes: always the current mode; adds `dark` for compatibility; adds `amoled` when dark + AMOLED enabled.
|
||||
- AMOLED handling: overrides dark backgrounds to pure black while retaining other dark tokens.
|
||||
- Brand colors:
|
||||
- If theme provides brand colors, inline CSS variables are set.
|
||||
- If theme omits brand colors, inline brand variables are removed so the ColorPicker stylesheet takes effect.
|
||||
- Text colors:
|
||||
- Applied only if defined in the theme; otherwise defaults are used.
|
||||
- Custom logo:
|
||||
- If theme provides `logo`, sets `--vp-theme-logo: url(...)` for downstream usage.
|
||||
|
||||
## UI Components
|
||||
|
||||
- `ThemeDropdown.vue`: replaces the appearance toggle.
|
||||
- Options: Light, Dark, AMOLED (as dark variant).
|
||||
- Stores/reads mode and AMOLED-enabled state.
|
||||
- Aliased via `docs/.vitepress/config.mts` to override `VPSwitchAppearance.vue`.
|
||||
- `ColorPicker.vue`:
|
||||
- Controls brand color CSS variables via a stylesheet tag (`#brand-color`).
|
||||
- Reapplies colors on a custom event `theme-changed-apply-colors` when switching to themes without brand.
|
||||
- `ThemeSelector.vue`:
|
||||
- Shows circular previews per theme (image via `preview` or gradient fallback).
|
||||
- Calls `setTheme(name)`; independent from ColorPicker.
|
||||
|
||||
## Theme Registry (`configs/index.ts`)
|
||||
|
||||
- Example:
|
||||
```ts
|
||||
import { catppuccinTheme } from './catppuccin'
|
||||
|
||||
export const themeRegistry = {
|
||||
default: catppuccinTheme,
|
||||
catppuccin: catppuccinTheme
|
||||
}
|
||||
```
|
||||
|
||||
## Creating a Theme (`configs/<name>.ts`)
|
||||
|
||||
- Export a `Theme` object with:
|
||||
- `name`, `displayName`, optional `preview` (image URL/data) and `logo`.
|
||||
- `modes.light` and `modes.dark` objects.
|
||||
- Optional `fonts`, `spacing`, `borderRadius`, `customProperties`.
|
||||
- Register it in `configs/index.ts`.
|
||||
- If you omit `brand` in a mode, the ColorPicker-selected brand colors will be used.
|
||||
- If you omit `text` in a mode, VitePress default text colors will be used.
|
||||
|
||||
## CSS Variables
|
||||
|
||||
- Brand: `--vp-c-brand-1`, `--vp-c-brand-2`, `--vp-c-brand-3`, `--vp-c-brand-soft`.
|
||||
- Background: `--vp-c-bg`, `--vp-c-bg-alt`, `--vp-c-bg-elv`, `--vp-c-bg-mark`.
|
||||
- Text: `--vp-c-text-1`, `--vp-c-text-2`, `--vp-c-text-3`.
|
||||
- Buttons: `--vp-button-brand-*`, `--vp-button-alt-*`.
|
||||
- Custom blocks: `--vp-custom-block-{type}-*`.
|
||||
- Selection: `--vp-c-selection-bg`.
|
||||
- Home hero: `--vp-home-hero-*`.
|
||||
- Custom props: all keys in `customProperties`.
|
||||
- Optional: `--vp-theme-logo` (when theme defines `logo`).
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- AMOLED is no longer a separate mode; it’s a dark enhancement (pure black backgrounds) toggled in the dropdown.
|
||||
- The legacy `Appearance.vue` toggle is replaced by `ThemeDropdown.vue` via alias in `config.mts`.
|
||||
- Themes can rely on the ColorPicker for brand colors by omitting `brand`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Theme not applying: ensure it’s added to `themeRegistry` and named correctly.
|
||||
- Brand not changing: if a theme sets inline brand variables, ColorPicker won’t override; remove `brand` from the theme to defer to ColorPicker.
|
||||
- Colors not updating after theme switch: ColorPicker listens for `theme-changed-apply-colors`; make sure that event dispatch remains in `setTheme()`.
|
||||
- AMOLED not pure black: confirm dark mode is active and AMOLED toggle is enabled; handler overrides backgrounds when enabled.
|
||||
|
||||
161
docs/.vitepress/theme/themes/configs/catppuccin.ts
Normal file
161
docs/.vitepress/theme/themes/configs/catppuccin.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
* Copyright (c) 2025 taskylizard. Apache License 2.0.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Theme } from '../types'
|
||||
|
||||
export const catppuccinTheme: Theme = {
|
||||
name: 'catppuccin',
|
||||
displayName: 'Catppuccin',
|
||||
preview: 'https://raw.githubusercontent.com/catppuccin/catppuccin/main/assets/logos/exports/1544x1544_circle.png',
|
||||
modes: {
|
||||
light: {
|
||||
brand: {
|
||||
1: '#8b5cf6',
|
||||
2: '#7c3aed',
|
||||
3: '#5b21b6',
|
||||
soft: '#a78bfa'
|
||||
},
|
||||
bg: '#ffffff',
|
||||
bgAlt: '#f9fafb',
|
||||
bgElv: 'rgba(255, 255, 255, 0.7)',
|
||||
bgMark: 'rgb(232, 232, 232)',
|
||||
text: {
|
||||
1: '#1f2937',
|
||||
2: '#4b5563',
|
||||
3: '#6b7280'
|
||||
},
|
||||
button: {
|
||||
brand: {
|
||||
bg: '#8b5cf6',
|
||||
border: '#a78bfa',
|
||||
text: 'rgba(42, 40, 47)',
|
||||
hoverBorder: '#a78bfa',
|
||||
hoverText: 'rgba(42, 40, 47)',
|
||||
hoverBg: '#a78bfa',
|
||||
activeBorder: '#a78bfa',
|
||||
activeText: 'rgba(42, 40, 47)',
|
||||
activeBg: '#8b5cf6'
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: '#ede9fe',
|
||||
border: '#5b21b6',
|
||||
text: '#5b21b6',
|
||||
textDeep: '#4c1d95'
|
||||
},
|
||||
tip: {
|
||||
bg: '#d1fae5',
|
||||
border: '#065f46',
|
||||
text: '#065f46',
|
||||
textDeep: '#064e3b'
|
||||
},
|
||||
warning: {
|
||||
bg: '#fef3c7',
|
||||
border: '#92400e',
|
||||
text: '#92400e',
|
||||
textDeep: '#78350f'
|
||||
},
|
||||
danger: {
|
||||
bg: '#ffe4e6',
|
||||
border: '#9f1239',
|
||||
text: '#9f1239',
|
||||
textDeep: '#881337'
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
bg: '#5586a6'
|
||||
},
|
||||
home: {
|
||||
heroNameColor: 'transparent',
|
||||
heroNameBackground: '-webkit-linear-gradient(120deg, #c4b5fd 30%, #7bc5e4)',
|
||||
heroImageBackground: 'linear-gradient(-45deg, #c4b5fd 50%, #47caff 50%)',
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
},
|
||||
dark: {
|
||||
brand: {
|
||||
1: '#a78bfa',
|
||||
2: '#8b5cf6',
|
||||
3: '#6d28d9',
|
||||
soft: '#c4b5fd'
|
||||
},
|
||||
bg: 'rgb(26, 26, 26)',
|
||||
bgAlt: 'rgb(23, 23, 23)',
|
||||
bgElv: 'rgba(23, 23, 23, 0.8)',
|
||||
button: {
|
||||
brand: {
|
||||
bg: '#a78bfa',
|
||||
border: '#c4b5fd',
|
||||
text: 'rgba(42, 40, 47)',
|
||||
hoverBorder: '#c4b5fd',
|
||||
hoverText: 'rgba(42, 40, 47)',
|
||||
hoverBg: '#c4b5fd',
|
||||
activeBorder: '#c4b5fd',
|
||||
activeText: 'rgba(42, 40, 47)',
|
||||
activeBg: '#a78bfa'
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: '#2e1065',
|
||||
border: '#5b21b6',
|
||||
text: '#ddd6fe',
|
||||
textDeep: '#ddd6fe'
|
||||
},
|
||||
tip: {
|
||||
bg: '#022c22',
|
||||
border: '#065f46',
|
||||
text: '#a7f3d0',
|
||||
textDeep: '#a7f3d0'
|
||||
},
|
||||
warning: {
|
||||
bg: '#451a03',
|
||||
border: '#92400e',
|
||||
text: '#fef08a',
|
||||
textDeep: '#fef08a'
|
||||
},
|
||||
danger: {
|
||||
bg: '#4c0519',
|
||||
border: '#9f1239',
|
||||
text: '#fecdd3',
|
||||
textDeep: '#fecdd3'
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
bg: '#0f2c47'
|
||||
},
|
||||
home: {
|
||||
heroNameColor: 'transparent',
|
||||
heroNameBackground: '-webkit-linear-gradient(120deg, #c4b5fd 30%, #7bc5e4)',
|
||||
heroImageBackground: 'linear-gradient(-45deg, #c4b5fd 50%, #47caff 50%)',
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
161
docs/.vitepress/theme/themes/configs/christmas.ts
Normal file
161
docs/.vitepress/theme/themes/configs/christmas.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
* Copyright (c) 2025 taskylizard. Apache License 2.0.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Theme } from '../types'
|
||||
|
||||
export const christmasTheme: Theme = {
|
||||
name: 'Christmas',
|
||||
displayName: 'Christmas',
|
||||
preview: 'https://raw.githubusercontent.com/SamidyFR/edit/refs/heads/main/docs/.vitepress/theme/themes/configs/christmas_tree.png',
|
||||
modes: {
|
||||
light: {
|
||||
brand: {
|
||||
1: '#BD2F2F',
|
||||
2: '#22ff00ff',
|
||||
3: '#155C2F',
|
||||
soft: '#a200ffff'
|
||||
},
|
||||
bg: '#ffffffff',
|
||||
bgAlt: '#f9fafb',
|
||||
bgElv: 'rgba(255, 255, 255, 0.7)',
|
||||
bgMark: 'rgb(232, 232, 232)',
|
||||
text: {
|
||||
1: '#1f2937',
|
||||
2: '#4b5563',
|
||||
3: '#353638ff'
|
||||
},
|
||||
button: {
|
||||
brand: {
|
||||
bg: '#155C2F',
|
||||
border: '#0E3B1F',
|
||||
text: 'rgba(255, 255, 255)',
|
||||
hoverBorder: '#072a15ff',
|
||||
hoverText: 'rgba(255, 255, 255)',
|
||||
hoverBg: '#072a15ff',
|
||||
activeBorder: '#072a15ff',
|
||||
activeText: 'rgba(255, 255, 255)',
|
||||
activeBg: '#072a15ff'
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: '#dbeafe',
|
||||
border: '#1e40af',
|
||||
text: '#1e40af',
|
||||
textDeep: '#1e3a8a'
|
||||
},
|
||||
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: '#bfdbfe'
|
||||
},
|
||||
home: {
|
||||
heroNameColor: 'transparent',
|
||||
heroNameBackground: '-webkit-linear-gradient(120deg, #BD2F2F 30%, #f9fafb)',
|
||||
heroImageBackground: 'linear-gradient(-45deg, #BD2F2F 50%, #f9fafb 50%)',
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
},
|
||||
dark: {
|
||||
brand: {
|
||||
1: '#2CA03C',
|
||||
2: '#22ff00ff',
|
||||
3: '#5C151A',
|
||||
soft: '#a200ffff'
|
||||
},
|
||||
bg: 'rgb(26, 26, 26)',
|
||||
bgAlt: 'rgb(23, 23, 23)',
|
||||
bgElv: 'rgba(23, 23, 23, 0.8)',
|
||||
button: {
|
||||
brand: {
|
||||
bg: '#155C2F',
|
||||
border: '#0E3B1F',
|
||||
text: 'rgba(255, 255, 255)',
|
||||
hoverBorder: '#072a15ff',
|
||||
hoverText: 'rgba(255, 255, 255)',
|
||||
hoverBg: '#072a15ff',
|
||||
activeBorder: '#072a15ff',
|
||||
activeText: 'rgba(255, 255, 255)',
|
||||
activeBg: '#072a15ff'
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: '#0c4a6e',
|
||||
border: '#0284c7',
|
||||
text: '#bae6fd',
|
||||
textDeep: '#bae6fd'
|
||||
},
|
||||
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: '#1e3a8a'
|
||||
},
|
||||
home: {
|
||||
heroNameColor: 'transparent',
|
||||
heroNameBackground: '-webkit-linear-gradient(120deg, #f9fafb 30%, #BD2F2F)',
|
||||
heroImageBackground: 'linear-gradient(-45deg, #f9fafb 50%,#BD2F2F 50%)',
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
docs/.vitepress/theme/themes/configs/christmas_tree.png
Normal file
BIN
docs/.vitepress/theme/themes/configs/christmas_tree.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
161
docs/.vitepress/theme/themes/configs/dark.ts
Normal file
161
docs/.vitepress/theme/themes/configs/dark.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
* Copyright (c) 2025 taskylizard. Apache License 2.0.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Theme } from '../types'
|
||||
|
||||
export const darkTheme: Theme = {
|
||||
name: 'Christmas',
|
||||
displayName: 'Christmas',
|
||||
preview: 'https://files.catbox.moe/inbi62.png',
|
||||
modes: {
|
||||
light: {
|
||||
brand: {
|
||||
1: '#BD2F2F',
|
||||
2: '#22ff00ff',
|
||||
3: '#155C2F',
|
||||
soft: '#a200ffff'
|
||||
},
|
||||
bg: '#ffffffff',
|
||||
bgAlt: '#f9fafb',
|
||||
bgElv: 'rgba(255, 255, 255, 0.7)',
|
||||
bgMark: 'rgb(232, 232, 232)',
|
||||
text: {
|
||||
1: '#1f2937',
|
||||
2: '#4b5563',
|
||||
3: '#353638ff'
|
||||
},
|
||||
button: {
|
||||
brand: {
|
||||
bg: '#155C2F',
|
||||
border: '#0E3B1F',
|
||||
text: 'rgba(255, 255, 255)',
|
||||
hoverBorder: '#072a15ff',
|
||||
hoverText: 'rgba(255, 255, 255)',
|
||||
hoverBg: '#072a15ff',
|
||||
activeBorder: '#072a15ff',
|
||||
activeText: 'rgba(255, 255, 255)',
|
||||
activeBg: '#072a15ff'
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: '#dbeafe',
|
||||
border: '#1e40af',
|
||||
text: '#1e40af',
|
||||
textDeep: '#1e3a8a'
|
||||
},
|
||||
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: '#bfdbfe'
|
||||
},
|
||||
home: {
|
||||
heroNameColor: 'transparent',
|
||||
heroNameBackground: '-webkit-linear-gradient(120deg, #BD2F2F 30%, #f9fafb)',
|
||||
heroImageBackground: 'linear-gradient(-45deg, #BD2F2F 50%, #f9fafb 50%)',
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
},
|
||||
dark: {
|
||||
brand: {
|
||||
1: '#2CA03C',
|
||||
2: '#22ff00ff',
|
||||
3: '#5C151A',
|
||||
soft: '#a200ffff'
|
||||
},
|
||||
bg: 'rgb(26, 26, 26)',
|
||||
bgAlt: 'rgb(23, 23, 23)',
|
||||
bgElv: 'rgba(23, 23, 23, 0.8)',
|
||||
button: {
|
||||
brand: {
|
||||
bg: '#155C2F',
|
||||
border: '#0E3B1F',
|
||||
text: 'rgba(255, 255, 255)',
|
||||
hoverBorder: '#072a15ff',
|
||||
hoverText: 'rgba(255, 255, 255)',
|
||||
hoverBg: '#072a15ff',
|
||||
activeBorder: '#072a15ff',
|
||||
activeText: 'rgba(255, 255, 255)',
|
||||
activeBg: '#072a15ff'
|
||||
},
|
||||
alt: {
|
||||
bg: '#484848',
|
||||
text: '#f0eeee',
|
||||
hoverBg: '#484848',
|
||||
hoverText: '#f0eeee'
|
||||
}
|
||||
},
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: '#0c4a6e',
|
||||
border: '#0284c7',
|
||||
text: '#bae6fd',
|
||||
textDeep: '#bae6fd'
|
||||
},
|
||||
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: '#1e3a8a'
|
||||
},
|
||||
home: {
|
||||
heroNameColor: 'transparent',
|
||||
heroNameBackground: '-webkit-linear-gradient(120deg, #f9fafb 30%, #BD2F2F)',
|
||||
heroImageBackground: 'linear-gradient(-45deg, #f9fafb 50%,#BD2F2F 50%)',
|
||||
heroImageFilter: 'blur(44px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
docs/.vitepress/theme/themes/configs/index.ts
Normal file
25
docs/.vitepress/theme/themes/configs/index.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Copyright (c) 2025 taskylizard. Apache License 2.0.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { christmasTheme } from './christmas'
|
||||
import { catppuccinTheme } from './catppuccin'
|
||||
import type { ThemeRegistry } from '../types'
|
||||
|
||||
export const themeRegistry: ThemeRegistry = {
|
||||
catppuccin: catppuccinTheme,
|
||||
christmas: christmasTheme,
|
||||
}
|
||||
|
||||
export { christmasTheme, catppuccinTheme }
|
||||
19
docs/.vitepress/theme/themes/index.ts
Normal file
19
docs/.vitepress/theme/themes/index.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Copyright (c) 2025 taskylizard. Apache License 2.0.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './types'
|
||||
export * from './themeHandler'
|
||||
export * from './configs'
|
||||
395
docs/.vitepress/theme/themes/themeHandler.ts
Normal file
395
docs/.vitepress/theme/themes/themeHandler.ts
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
/**
|
||||
* Copyright (c) 2025 taskylizard. Apache License 2.0.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import type { DisplayMode, ThemeState, Theme, ModeColors } from './types'
|
||||
import { themeRegistry } from './configs'
|
||||
|
||||
const STORAGE_KEY_THEME = 'vitepress-theme-name'
|
||||
const STORAGE_KEY_MODE = 'vitepress-display-mode'
|
||||
const STORAGE_KEY_AMOLED = 'vitepress-amoled-enabled'
|
||||
|
||||
export class ThemeHandler {
|
||||
private state = ref<ThemeState>({
|
||||
currentTheme: 'christmas',
|
||||
currentMode: 'light' as DisplayMode,
|
||||
theme: themeRegistry.christmas
|
||||
})
|
||||
private amoledEnabled = ref(false)
|
||||
|
||||
constructor() {
|
||||
this.initializeTheme()
|
||||
}
|
||||
|
||||
private initializeTheme() {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
// Load saved preferences
|
||||
const savedTheme = localStorage.getItem(STORAGE_KEY_THEME) || 'christmas'
|
||||
const savedMode = localStorage.getItem(STORAGE_KEY_MODE) as DisplayMode | null
|
||||
const savedAmoled = localStorage.getItem(STORAGE_KEY_AMOLED) === 'true'
|
||||
|
||||
// Set theme
|
||||
if (themeRegistry[savedTheme]) {
|
||||
this.state.value.currentTheme = savedTheme
|
||||
this.state.value.theme = themeRegistry[savedTheme]
|
||||
}
|
||||
|
||||
// Set amoled preference
|
||||
this.amoledEnabled.value = savedAmoled
|
||||
|
||||
// Set mode
|
||||
if (savedMode) {
|
||||
this.state.value.currentMode = savedMode
|
||||
} else {
|
||||
// Detect system preference for initial mode
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
this.state.value.currentMode = prefersDark ? 'dark' : 'light'
|
||||
}
|
||||
|
||||
this.applyTheme()
|
||||
|
||||
// Listen for system theme changes (only if user hasn't set a preference)
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||||
if (!localStorage.getItem(STORAGE_KEY_MODE)) {
|
||||
this.state.value.currentMode = e.matches ? 'dark' : 'light'
|
||||
this.applyTheme()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private applyTheme() {
|
||||
if (typeof document === 'undefined') return
|
||||
|
||||
const { currentMode, theme } = this.state.value
|
||||
const modeColors = theme.modes[currentMode]
|
||||
|
||||
this.applyDOMClasses(currentMode)
|
||||
this.applyCSSVariables(modeColors, theme)
|
||||
}
|
||||
|
||||
private applyDOMClasses(mode: DisplayMode) {
|
||||
const root = document.documentElement
|
||||
|
||||
// Remove all mode classes
|
||||
root.classList.remove('dark', 'light', 'amoled')
|
||||
|
||||
// Add current mode class
|
||||
root.classList.add(mode)
|
||||
|
||||
// Add amoled class if enabled in dark mode
|
||||
if (mode === 'dark' && this.amoledEnabled.value) {
|
||||
root.classList.add('amoled')
|
||||
}
|
||||
|
||||
// Add dark class for backward compatibility with VitePress
|
||||
if (mode === 'dark') {
|
||||
root.classList.add('dark')
|
||||
}
|
||||
}
|
||||
|
||||
private applyCSSVariables(colors: ModeColors, theme: Theme) {
|
||||
if (typeof document === 'undefined') return
|
||||
|
||||
const root = document.documentElement
|
||||
|
||||
// Clear ALL inline styles related to theming to ensure clean slate
|
||||
const allStyleProps = Array.from(root.style)
|
||||
allStyleProps.forEach(prop => {
|
||||
if (prop.startsWith('--vp-')) {
|
||||
root.style.removeProperty(prop)
|
||||
}
|
||||
})
|
||||
let bgColor = colors.bg
|
||||
let bgAltColor = colors.bgAlt
|
||||
let bgElvColor = colors.bgElv
|
||||
|
||||
if (this.state.value.currentMode === 'dark' && this.amoledEnabled.value) {
|
||||
bgColor = '#000000'
|
||||
bgAltColor = '#000000'
|
||||
bgElvColor = 'rgba(0, 0, 0, 0.9)'
|
||||
}
|
||||
|
||||
// Apply brand colors only if theme specifies them
|
||||
// Otherwise, remove inline styles to let ColorPicker CSS take effect
|
||||
if (colors.brand && (colors.brand[1] || colors.brand[2] || colors.brand[3] || colors.brand.soft)) {
|
||||
if (colors.brand[1]) root.style.setProperty('--vp-c-brand-1', colors.brand[1])
|
||||
if (colors.brand[2]) root.style.setProperty('--vp-c-brand-2', colors.brand[2])
|
||||
if (colors.brand[3]) root.style.setProperty('--vp-c-brand-3', colors.brand[3])
|
||||
if (colors.brand.soft) root.style.setProperty('--vp-c-brand-soft', colors.brand.soft)
|
||||
} else {
|
||||
// Remove inline brand color styles so ColorPicker CSS can apply
|
||||
root.style.removeProperty('--vp-c-brand-1')
|
||||
root.style.removeProperty('--vp-c-brand-2')
|
||||
root.style.removeProperty('--vp-c-brand-3')
|
||||
root.style.removeProperty('--vp-c-brand-soft')
|
||||
}
|
||||
|
||||
// Apply background colors
|
||||
root.style.setProperty('--vp-c-bg', bgColor)
|
||||
root.style.setProperty('--vp-c-bg-alt', bgAltColor)
|
||||
root.style.setProperty('--vp-c-bg-elv', bgElvColor)
|
||||
if (colors.bgMark) {
|
||||
root.style.setProperty('--vp-c-bg-mark', colors.bgMark)
|
||||
}
|
||||
|
||||
// Apply text colors - always set them to ensure proper theme switching
|
||||
if (colors.text) {
|
||||
if (colors.text[1]) root.style.setProperty('--vp-c-text-1', colors.text[1])
|
||||
if (colors.text[2]) root.style.setProperty('--vp-c-text-2', colors.text[2])
|
||||
if (colors.text[3]) root.style.setProperty('--vp-c-text-3', colors.text[3])
|
||||
} else {
|
||||
// Remove inline styles if theme doesn't specify text colors
|
||||
// This allows CSS variables from style.scss to take effect
|
||||
root.style.removeProperty('--vp-c-text-1')
|
||||
root.style.removeProperty('--vp-c-text-2')
|
||||
root.style.removeProperty('--vp-c-text-3')
|
||||
}
|
||||
|
||||
// Debug: log applied text color variables so we can inspect in console
|
||||
try {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[ThemeHandler] applied text vars', {
|
||||
theme: theme.name,
|
||||
mode: this.state.value.currentMode,
|
||||
vp_text_1: root.style.getPropertyValue('--vp-c-text-1'),
|
||||
vp_text_2: root.style.getPropertyValue('--vp-c-text-2'),
|
||||
vp_text_3: root.style.getPropertyValue('--vp-c-text-3')
|
||||
})
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Apply button colors
|
||||
root.style.setProperty('--vp-button-brand-bg', colors.button.brand.bg)
|
||||
root.style.setProperty('--vp-button-brand-border', colors.button.brand.border)
|
||||
root.style.setProperty('--vp-button-brand-text', colors.button.brand.text)
|
||||
root.style.setProperty('--vp-button-brand-hover-border', colors.button.brand.hoverBorder)
|
||||
root.style.setProperty('--vp-button-brand-hover-text', colors.button.brand.hoverText)
|
||||
root.style.setProperty('--vp-button-brand-hover-bg', colors.button.brand.hoverBg)
|
||||
root.style.setProperty('--vp-button-brand-active-border', colors.button.brand.activeBorder)
|
||||
root.style.setProperty('--vp-button-brand-active-text', colors.button.brand.activeText)
|
||||
root.style.setProperty('--vp-button-brand-active-bg', colors.button.brand.activeBg)
|
||||
root.style.setProperty('--vp-button-alt-bg', colors.button.alt.bg)
|
||||
root.style.setProperty('--vp-button-alt-text', colors.button.alt.text)
|
||||
root.style.setProperty('--vp-button-alt-hover-bg', colors.button.alt.hoverBg)
|
||||
root.style.setProperty('--vp-button-alt-hover-text', colors.button.alt.hoverText)
|
||||
|
||||
// Apply custom block colors
|
||||
const blocks = ['info', 'tip', 'warning', 'danger'] as const
|
||||
blocks.forEach((block) => {
|
||||
const blockColors = colors.customBlock[block]
|
||||
root.style.setProperty(`--vp-custom-block-${block}-bg`, blockColors.bg)
|
||||
root.style.setProperty(`--vp-custom-block-${block}-border`, blockColors.border)
|
||||
root.style.setProperty(`--vp-custom-block-${block}-text`, blockColors.text)
|
||||
root.style.setProperty(`--vp-custom-block-${block}-text-deep`, blockColors.textDeep)
|
||||
})
|
||||
|
||||
// Apply selection color
|
||||
root.style.setProperty('--vp-c-selection-bg', colors.selection.bg)
|
||||
|
||||
// Apply home hero colors (if defined)
|
||||
if (colors.home) {
|
||||
root.style.setProperty('--vp-home-hero-name-color', colors.home.heroNameColor)
|
||||
root.style.setProperty('--vp-home-hero-name-background', colors.home.heroNameBackground)
|
||||
root.style.setProperty('--vp-home-hero-image-background-image', colors.home.heroImageBackground)
|
||||
root.style.setProperty('--vp-home-hero-image-filter', colors.home.heroImageFilter)
|
||||
} else {
|
||||
// Remove home hero color styles if theme doesn't specify them
|
||||
root.style.removeProperty('--vp-home-hero-name-color')
|
||||
root.style.removeProperty('--vp-home-hero-name-background')
|
||||
root.style.removeProperty('--vp-home-hero-image-background-image')
|
||||
root.style.removeProperty('--vp-home-hero-image-filter')
|
||||
}
|
||||
|
||||
// Apply fonts (if defined)
|
||||
if (theme.fonts?.body) {
|
||||
root.style.setProperty('--vp-font-family-base', theme.fonts.body)
|
||||
} else {
|
||||
root.style.removeProperty('--vp-font-family-base')
|
||||
}
|
||||
if (theme.fonts?.heading) {
|
||||
root.style.setProperty('--vp-font-family-heading', theme.fonts.heading)
|
||||
} else {
|
||||
root.style.removeProperty('--vp-font-family-heading')
|
||||
}
|
||||
|
||||
// Apply border radius (if defined)
|
||||
if (theme.borderRadius) {
|
||||
root.style.setProperty('--vp-border-radius', theme.borderRadius)
|
||||
} else {
|
||||
root.style.removeProperty('--vp-border-radius')
|
||||
}
|
||||
|
||||
// Apply spacing (if defined)
|
||||
if (theme.spacing) {
|
||||
if (theme.spacing.small) root.style.setProperty('--vp-spacing-small', theme.spacing.small)
|
||||
else root.style.removeProperty('--vp-spacing-small')
|
||||
if (theme.spacing.medium) root.style.setProperty('--vp-spacing-medium', theme.spacing.medium)
|
||||
else root.style.removeProperty('--vp-spacing-medium')
|
||||
if (theme.spacing.large) root.style.setProperty('--vp-spacing-large', theme.spacing.large)
|
||||
else root.style.removeProperty('--vp-spacing-large')
|
||||
} else {
|
||||
root.style.removeProperty('--vp-spacing-small')
|
||||
root.style.removeProperty('--vp-spacing-medium')
|
||||
root.style.removeProperty('--vp-spacing-large')
|
||||
}
|
||||
|
||||
// Apply custom properties (if defined)
|
||||
if (theme.customProperties) {
|
||||
Object.entries(theme.customProperties).forEach(([key, value]) => {
|
||||
root.style.setProperty(key, value)
|
||||
})
|
||||
}
|
||||
|
||||
// Apply custom logo (if defined)
|
||||
if (theme.logo) {
|
||||
root.style.setProperty('--vp-theme-logo', `url(${theme.logo})`)
|
||||
} else {
|
||||
root.style.removeProperty('--vp-theme-logo')
|
||||
}
|
||||
}
|
||||
|
||||
public setTheme(themeName: string) {
|
||||
if (!themeRegistry[themeName]) {
|
||||
console.warn(`Theme "${themeName}" not found. Using christmas theme.`)
|
||||
themeName = 'christmas'
|
||||
}
|
||||
|
||||
this.state.value.currentTheme = themeName
|
||||
this.state.value.theme = themeRegistry[themeName]
|
||||
localStorage.setItem(STORAGE_KEY_THEME, themeName)
|
||||
this.applyTheme()
|
||||
|
||||
// Force re-apply ColorPicker colors if theme doesn't specify brand colors
|
||||
this.ensureColorPickerColors()
|
||||
}
|
||||
|
||||
public setMode(mode: DisplayMode) {
|
||||
this.state.value.currentMode = mode
|
||||
localStorage.setItem(STORAGE_KEY_MODE, mode)
|
||||
this.applyTheme()
|
||||
}
|
||||
|
||||
public toggleMode() {
|
||||
const currentMode = this.state.value.currentMode
|
||||
|
||||
// Toggle between light and dark
|
||||
const newMode: DisplayMode = currentMode === 'light' ? 'dark' : 'light'
|
||||
|
||||
this.setMode(newMode)
|
||||
}
|
||||
|
||||
public setAmoledEnabled(enabled: boolean) {
|
||||
this.amoledEnabled.value = enabled
|
||||
localStorage.setItem(STORAGE_KEY_AMOLED, enabled.toString())
|
||||
this.applyTheme()
|
||||
}
|
||||
|
||||
public getAmoledEnabled() {
|
||||
return this.amoledEnabled.value
|
||||
}
|
||||
|
||||
public toggleAmoled() {
|
||||
this.setAmoledEnabled(!this.amoledEnabled.value)
|
||||
}
|
||||
|
||||
public getAmoledEnabledRef() {
|
||||
return this.amoledEnabled
|
||||
}
|
||||
|
||||
private ensureColorPickerColors() {
|
||||
// If theme doesn't specify brand colors, force ColorPicker to reapply its selection
|
||||
const currentMode = this.state.value.currentMode
|
||||
const modeColors = this.state.value.theme.modes[currentMode]
|
||||
|
||||
if (!modeColors.brand || !modeColors.brand[1]) {
|
||||
// Trigger a custom event that ColorPicker can listen to
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent('theme-changed-apply-colors'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getState() {
|
||||
return this.state
|
||||
}
|
||||
|
||||
public getMode() {
|
||||
return this.state.value.currentMode
|
||||
}
|
||||
|
||||
public getTheme() {
|
||||
return this.state.value.currentTheme
|
||||
}
|
||||
|
||||
public getCurrentTheme() {
|
||||
return this.state.value.theme
|
||||
}
|
||||
|
||||
public getAvailableThemes() {
|
||||
return Object.keys(themeRegistry).map(key => ({
|
||||
name: key,
|
||||
displayName: themeRegistry[key].displayName
|
||||
}))
|
||||
}
|
||||
|
||||
public isDarkMode() {
|
||||
const mode = this.state.value.currentMode
|
||||
return mode === 'dark'
|
||||
}
|
||||
|
||||
public isAmoledMode() {
|
||||
return this.state.value.currentMode === 'dark' && this.amoledEnabled.value
|
||||
}
|
||||
}
|
||||
|
||||
// Global theme handler instance
|
||||
let themeHandlerInstance: ThemeHandler | null = null
|
||||
|
||||
export function useThemeHandler() {
|
||||
if (!themeHandlerInstance) {
|
||||
themeHandlerInstance = new ThemeHandler()
|
||||
}
|
||||
return themeHandlerInstance
|
||||
}
|
||||
|
||||
// Composable for use in Vue components
|
||||
export function useTheme() {
|
||||
const handler = useThemeHandler()
|
||||
const state = handler.getState()
|
||||
|
||||
onMounted(() => {
|
||||
// Ensure theme is applied on mount
|
||||
handler.setMode(handler.getMode())
|
||||
})
|
||||
|
||||
return {
|
||||
mode: computed(() => state.value.currentMode),
|
||||
themeName: computed(() => state.value.currentTheme),
|
||||
theme: computed(() => state.value.theme),
|
||||
setMode: (mode: DisplayMode) => handler.setMode(mode),
|
||||
setTheme: (themeName: string) => handler.setTheme(themeName),
|
||||
toggleMode: () => handler.toggleMode(),
|
||||
getAvailableThemes: () => handler.getAvailableThemes(),
|
||||
isDarkMode: () => handler.isDarkMode(),
|
||||
isAmoledMode: () => handler.isAmoledMode(),
|
||||
amoledEnabled: handler.getAmoledEnabledRef(),
|
||||
setAmoledEnabled: (enabled: boolean) => handler.setAmoledEnabled(enabled),
|
||||
toggleAmoled: () => handler.toggleAmoled(),
|
||||
state
|
||||
}
|
||||
}
|
||||
134
docs/.vitepress/theme/themes/types.ts
Normal file
134
docs/.vitepress/theme/themes/types.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* Copyright (c) 2025 taskylizard. Apache License 2.0.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type DisplayMode = 'light' | 'dark'
|
||||
|
||||
export interface ModeColors {
|
||||
// Brand colors (optional - if not specified, ColorPicker values are used)
|
||||
brand?: {
|
||||
1?: string
|
||||
2?: string
|
||||
3?: string
|
||||
soft?: string
|
||||
}
|
||||
|
||||
// Background colors
|
||||
bg: string
|
||||
bgAlt: string
|
||||
bgElv: string
|
||||
bgMark?: string
|
||||
|
||||
// Text colors (optional - if not specified, VitePress defaults are used)
|
||||
text?: {
|
||||
1?: string
|
||||
2?: string
|
||||
3?: string
|
||||
}
|
||||
|
||||
// Button colors
|
||||
button: {
|
||||
brand: {
|
||||
bg: string
|
||||
border: string
|
||||
text: string
|
||||
hoverBorder: string
|
||||
hoverText: string
|
||||
hoverBg: string
|
||||
activeBorder: string
|
||||
activeText: string
|
||||
activeBg: string
|
||||
}
|
||||
alt: {
|
||||
bg: string
|
||||
text: string
|
||||
hoverBg: string
|
||||
hoverText: string
|
||||
}
|
||||
}
|
||||
|
||||
// Custom blocks
|
||||
customBlock: {
|
||||
info: {
|
||||
bg: string
|
||||
border: string
|
||||
text: string
|
||||
textDeep: string
|
||||
}
|
||||
tip: {
|
||||
bg: string
|
||||
border: string
|
||||
text: string
|
||||
textDeep: string
|
||||
}
|
||||
warning: {
|
||||
bg: string
|
||||
border: string
|
||||
text: string
|
||||
textDeep: string
|
||||
}
|
||||
danger: {
|
||||
bg: string
|
||||
border: string
|
||||
text: string
|
||||
textDeep: string
|
||||
}
|
||||
}
|
||||
|
||||
// Selection color
|
||||
selection: {
|
||||
bg: string
|
||||
}
|
||||
|
||||
// Home hero
|
||||
home?: {
|
||||
heroNameColor: string
|
||||
heroNameBackground: string
|
||||
heroImageBackground: string
|
||||
heroImageFilter: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
name: string
|
||||
displayName: string
|
||||
preview?: string // URL or path to theme preview image
|
||||
logo?: string // URL or path to custom logo
|
||||
modes: {
|
||||
light: ModeColors
|
||||
dark: ModeColors
|
||||
}
|
||||
fonts?: {
|
||||
body?: string
|
||||
heading?: string
|
||||
}
|
||||
borderRadius?: string
|
||||
spacing?: {
|
||||
small?: string
|
||||
medium?: string
|
||||
large?: string
|
||||
}
|
||||
customProperties?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface ThemeRegistry {
|
||||
[themeName: string]: Theme
|
||||
}
|
||||
|
||||
export interface ThemeState {
|
||||
currentTheme: string
|
||||
currentMode: DisplayMode
|
||||
theme: Theme
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
* ⭐ **[Microsoft Copilot](https://copilot.microsoft.com)** - GPT-5 / Unlimited / [Reasoning](https://github.com/fmhy/FMHY/wiki/FMHY%E2%80%90Notes.md#better-reasoning) / [Discord](https://discord.com/invite/go-copilot)
|
||||
* ⭐ **[Kimi](https://www.kimi.com/)** - Kimi K2 (Thinking) / Slides / Sign-Up Required / [Subreddit](https://www.reddit.com/r/kimi/) / [Discord](https://discord.gg/TYU2fdJykW) / [GitHub](https://github.com/MoonshotAI)
|
||||
* ⭐ **[Qwen](https://chat.qwen.ai/)** - Qwen3-Max / Unlimited / [Subreddit](https://www.reddit.com/r/Qwen_AI/) / [Discord](https://discord.com/invite/CV4E9rpNSD) / [GitHub](https://github.com/QwenLM)
|
||||
* ⭐ **[Grok](https://grok.com/)** - Grok 4 + Grok 3 Fast (96 daily) / Grok 4.1 (24 daily) / [Rate Display](https://greasyfork.org/en/scripts/533963) / [Subreddit](https://www.reddit.com/r/grok/) / [Discord](https://discord.com/invite/kqCc86jM55)
|
||||
* ⭐ **[Grok](https://grok.com/)** - Grok 4 + Grok 3 Fast (96 daily) / Grok 4.1 (24 daily) / [Rate Display](https://greasyfork.org/en/scripts/558017) / [Subreddit](https://www.reddit.com/r/grok/) / [Discord](https://discord.com/invite/kqCc86jM55)
|
||||
* ⭐ **[Z.ai](https://chat.z.ai/)** - GLM 4.6 / Slides / Unlimited / [Discord](https://discord.gg/QR7SARHRxK)
|
||||
* ⭐ **[DeepSeek](https://chat.deepseek.com/)** - DeepSeek-V3.2 / Sign-Up Required / Unlimited / [Subreddit](https://www.reddit.com/r/DeepSeek/) / [Discord](https://discord.com/invite/Tc7c45Zzu5) / [GitHub](https://github.com/deepseek-ai)
|
||||
* [Claude](https://claude.ai/) - Claude 4.5 Sonnet / Sign-Up with Phone # Required / [Usage Tracker](https://github.com/lugia19/Claude-Usage-Extension) / [Subreddit](https://www.reddit.com/r/ClaudeAI/) / [Discord](https://discord.com/invite/6PPFFzqPDZ)
|
||||
|
|
|
|||
|
|
@ -394,19 +394,16 @@
|
|||
|
||||
## ▷ Telegram Bots
|
||||
|
||||
* [BeatSpotBot](https://t.me/BeatSpotBot) - Spotify / Deezer / Tidal / Yandex / VK / FLAC / 25 Daily
|
||||
* [JioDLBot](https://t.me/JioDLBot) - JioSaavn / Gaana / FLAC
|
||||
* [Music_Hunters](https://t.me/MusicsHuntersbot) - Spotify / Apple / Tidal / Deezer / 320kb MP3
|
||||
* [DeezerMusicBot](https://t.me/DeezerMusicBot) - Deezer / Soundcloud / VK / 320kb MP3 / FLAC / [Support](https://t.me/DeezerMusicNews)
|
||||
* [deezload2bot](https://t.me/deezload2bot) - Deezer / 320kb MP3 / [Updates](https://t.me/DEDSEClulz)
|
||||
* [BeatSpotBot](https://t.me/BeatSpotBot) - Spotify /Apple / YouTube / FLAC / 25 Daily
|
||||
* [Motreeb](https://t.me/motreb_downloader_bot) - Spotify / 320kb MP3
|
||||
* [scdlbot](https://t.me/scdlbot) - YouTube / SoundCloud / Bandcamp / Mixcloud / 128kb MP3
|
||||
* [vkmusbot](https://t.me/vkmusbot) or [Meph Bot](https://t.me/mephbot) - VK / 320kb MP3
|
||||
* [soundcloudaudiodownloader](https://t.me/soundcloudaudiodownloader) - YouTube / SoundCloud / 128kb MP3
|
||||
* [GlomaticoBlueMusicBot](https://t.me/GlomaticoBlueMusicBot) - Amazon Music Downloader / [Telegram](https://t.me/GlomaticoBotSupport) / [Discord](https://discord.gg/aBjMEZ9tnq)
|
||||
* [GlomaticoPinkMusicBot](https://t.me/GlomaticoPinkMusicBot) - Apple Music Downloader / [Telegram](https://t.me/GlomaticoBotSupport) / [Discord](https://discord.gg/aBjMEZ9tnq)
|
||||
* [DeezerMusicBot](https://t.me/DeezerMusicBot) - Deezer / 320kb MP3 / FLAC
|
||||
* [deezload2bot](https://t.me/deezload2bot) - Deezer / 320kb MP3
|
||||
* [Music_Hunters](https://t.me/MusicsHuntersbot) - Deezer / 320kb MP3
|
||||
* [Motreeb](https://t.me/motreb_downloader_bot) - Spotify / 320kb MP3
|
||||
* [GetSpotifyBot](https://t.me/GetSpotifyBot) - Spotify / 320kb MP3
|
||||
* [scdlbot](https://t.me/scdlbot) - YouTube / SoundCloud / Bandcamp / 128kb MP3
|
||||
* [soundcloudaudiodownloader](https://t.me/soundcloudaudiodownloader) - YouTube / SoundCloud / 128kb MP3
|
||||
* [VK Bot](https://t.me/vkmsaverbot), [VK Music Bot](https://t.me/vkmusic_bot), [vkmusbot](https://t.me/vkmusbot) or [Meph Bot](https://t.me/mephbot) - VK / 320kb MP3
|
||||
* [Song_downloaderbot](https://t.me/Song_downloaderbot) - JioSaavn / 128kb MP3
|
||||
|
||||
***
|
||||
|
||||
|
|
|
|||
|
|
@ -237,9 +237,8 @@
|
|||
# ► Debrid / Leeches
|
||||
|
||||
* 🌐 **[Debrid Services Comparison](https://debridcompare.xyz)** / [GitHub](https://github.com/fynks/debrid-services-comparison)
|
||||
* ⭐ **[TorBox](https://torbox.app/)** - Freemium / 10GB / 10 Monthly Downloads / Sign-Up Required / [Unofficial Mobile Client](https://github.com/93Pd9s8Jt/atba) / [Subreddit](https://www.reddit.com/r/TorBoxApp/) / [Discord](https://discord.com/invite/wamy) / [GitHub](https://github.com/TorBox-App)
|
||||
* ⭐ **[Real-Debrid](https://real-debrid.com/)** - Paid Debrid Service / [Android Client](https://github.com/LivingWithHippos/unchained-android) / [Torrent Client](https://github.com/rogerfar/rdt-client) / [DDL Client](https://github.com/ItsYeBoi20/TorrentDownloaderRD)
|
||||
* ⭐ **[HDEncode](https://hdencode.org/)**, [DDLBase](https://ddlbase.com/), [RapidMoviez](https://rmz.cr/) / [Mirrors](https://rmzmirrors.com/) or [rlsDB](https://rlsdb.com/) - Movie & TV DDL Forums / Requires Debrid
|
||||
* ⭐ **[TorBox](https://torbox.app/)** - Paid / Sign-Up Required / [Unofficial Mobile Client](https://github.com/93Pd9s8Jt/atba) / [Subreddit](https://www.reddit.com/r/TorBoxApp/) / [Discord](https://discord.com/invite/wamy) / [GitHub](https://github.com/TorBox-App)
|
||||
* ⭐ **[Real-Debrid](https://real-debrid.com/)** - Paid / [Android Client](https://github.com/LivingWithHippos/unchained-android) / [Torrent Client](https://github.com/rogerfar/rdt-client) / [DDL Client](https://github.com/ItsYeBoi20/TorrentDownloaderRD)
|
||||
* [Multi-OCH Helper](https://greasyfork.org/en/scripts/13884-multi-och-helper) - Quickly Send DDL Links to Premiumize & NoPremium
|
||||
* [Debrid Media Manager](https://debridmediamanager.com/) - Manage / Stream / Download Debrid Files
|
||||
|
||||
|
|
|
|||
|
|
@ -128,7 +128,6 @@
|
|||
|
||||
* ↪️ **[Photography / Cameras](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/image-tools#wiki_.25BA_photography_.2F_cameras)**
|
||||
* ↪️ **[Typing Tests / Games](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/text-tools#wiki_.25B7_typing_lessons)**
|
||||
* [Rookie Road](https://www.rookieroad.com/) - Sport Guides
|
||||
* [Make it Yourself](https://makeityourself.org/) - 1000 DIY Projects / [Video](https://youtu.be/TSFJ2OH1PQA)
|
||||
* [Animated Knots](https://www.animatedknots.com/) or [NetKnots](https://www.netknots.com/) - Learn to Tie Knots
|
||||
* [Ian's Shoelace Site](https://www.fieggen.com/shoelace/) - Learn to Tie Shoelaces
|
||||
|
|
@ -213,6 +212,7 @@
|
|||
* ⭐ **[Stanford Encyclopedia of Philosophy](https://plato.stanford.edu/index.html)**, [IEP](https://iep.utm.edu/) or [nLab Philosophy](https://ncatlab.org/nlab/show/philosophy) - Philosophy Encyclopedias / [Search](https://www.visualizingsep.com/)
|
||||
* [Philosophy Bro](https://www.philosophybro.com/), [Reasoned](https://www.reasoned.org/dir/), [TheDailyIdea](https://thedailyidea.org/) or [PhilosophyBasics](https://www.philosophybasics.com/) - Philosophy Resources / Learning
|
||||
* [Wireless Philosophy](https://www.wi-phi.com/) - Philosophy Videos
|
||||
* [1000-Word Philosophy](https://1000wordphilosophy.com/) - 1000-Word Essays on Philosophical Topics
|
||||
* [Untools](https://untools.co/) - Better Thinking Tools
|
||||
* [Brainkit](https://www.braink.it/) - Learn Helpful Principles
|
||||
* [Art of Manliness](https://www.artofmanliness.com/) - Develop Life Skills
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@
|
|||
* [Alpha Beta Gamer](https://alphabetagamer.com/) - Play Games in Alpha / Beta Testing / [Discord](https://discord.gg/3Gtqp9BDeY)
|
||||
* [Necromanthus](https://necromanthus.com/) - 3D Shockwave Games
|
||||
* [LemmaSoft](https://lemmasoft.renai.us/) - Visual Novel Games / [Discord](https://discord.gg/6ckxWYm)
|
||||
* [Visual Novels Android](https://t.me/visual_novels_android_eng) - Android Visual Novel Ports
|
||||
* [vgperson](https://vgperson.com/games/) - Simple Japanese Games
|
||||
* [Visual Pinball](https://github.com/vpinball/vpinball) - Pinball Table Editor / Simulator / [Tables](https://www.vpforums.org/)
|
||||
* [Ninja Kiwi Archive](https://ninjakiwi.com/archive) - Ninja Kiwi / Bloons Archive / [Subreddit](https://www.reddit.com/r/NinjaKiwiOfficial/) / [Discord](https://discord.com/invite/ninjakiwi)
|
||||
|
|
|
|||
|
|
@ -580,7 +580,7 @@
|
|||
* [Polymaker](https://wiki.polymaker.com/) / [Discord](https://discord.com/invite/polymaker) or [Teaching Tech](https://teachingtechyt.github.io/index.html) / [GitHub](https://github.com/teachingtechYT/teachingtechYT.github.io) - 3D Printer Guides / Learning
|
||||
* [3D Printer Recs](https://redd.it/1bh9jud) - Hobbyist 3D Printer Recommendations
|
||||
* [SpoolScout](https://www.spoolscout.com/) - Search / Compare 3D Printing Filament Prices
|
||||
* [OrcaSlicer](https://www.orcaslicer.com/) / [X](https://x.com/real_OrcaSlicer) / [Discord](https://discord.gg/P4VE9UY9gJ) / [GitHub](https://github.com/OrcaSlicer/OrcaSlicer), [PrusaSlicer](https://help.prusa3d.com/product/prusaslicer / [GitHub](https://github.com/prusa3d/PrusaSlicer) or [Ultimaker Cura](https://ultimaker.com/software/ultimaker-cura) - 3D Printing Software
|
||||
* [OrcaSlicer](https://www.orcaslicer.com/) / [X](https://x.com/real_OrcaSlicer) / [Discord](https://discord.gg/P4VE9UY9gJ) / [GitHub](https://github.com/OrcaSlicer/OrcaSlicer), [PrusaSlicer](https://help.prusa3d.com/product/prusaslicer) / [GitHub](https://github.com/prusa3d/PrusaSlicer) or [Ultimaker Cura](https://ultimaker.com/software/ultimaker-cura) - 3D Printing Software
|
||||
* [e-NABLE](https://enablingthefuture.org/) - Volunteer Own 3D Printer for use in Prosthetic Limb Printing
|
||||
|
||||
***
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ hero:
|
|||
title: Dec 2025 Updates ❄️
|
||||
link: /posts/dec-2025
|
||||
image:
|
||||
src: /test.png
|
||||
src: /xmasfmhy.png
|
||||
alt: FMHY Icon
|
||||
actions:
|
||||
- theme: brand
|
||||
|
|
@ -175,7 +175,7 @@ onMounted(() => {
|
|||
const resetKawaii = () => {
|
||||
const images = document.querySelectorAll('.VPImage.image-src')
|
||||
images.forEach((img) => {
|
||||
img.src = '/test.png'
|
||||
img.src = '/xmasfmhy.png'
|
||||
})
|
||||
}
|
||||
if (kawaii === 'true') {
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@
|
|||
|
||||
## ▷ Chat Tools
|
||||
|
||||
* ↪️ **[Discord Tools](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/social-media#wiki_.25BA_discord_tools)**
|
||||
* ↪️ **[Telegram Tools](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/social-media#wiki_.25BA_telegram_tools)**
|
||||
* ↪️ **[Encrypted Messengers](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/adblock-vpn-privacy#wiki_.25B7_encrypted_messengers)**
|
||||
* ↪️ **[IRC Clients / Tools](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/download#wiki_.25B7_irc_tools)**
|
||||
* ⭐ **[Mumble](https://www.mumble.info/)**, [Jam](https://jam.systems/), [TeaSpeak](https://teaspeak.de/gb/) or [TeamSpeak](https://www.teamspeak.com/) / [Warning](https://github.com/fmhy/FMHY/wiki/FMHY%E2%80%90Notes.md#teamspeak-warning) - Voice Chat
|
||||
|
|
@ -409,7 +411,6 @@
|
|||
* [Got Your Back](https://github.com/GAM-team/got-your-back) - Backup Gmail Messages
|
||||
* [ExtractMailAddress](https://extractemailaddress.com/) - Extract Emails, URLs, and Numbers from Text
|
||||
* [scr.im](http://scr.im/) - Email Captcha Protection
|
||||
* [BugMeNot](https://bugmenot.com/), [FreeAccount](https://freeaccount.biz/) or [Password Login](https://password-login.com/) - Access & Share Throwaway Accounts
|
||||
|
||||
***
|
||||
|
||||
|
|
|
|||
|
|
@ -376,7 +376,6 @@
|
|||
* [ZapZap](https://rtosta.com/zapzap/) - WhatsApp Client / [GitHub](https://github.com/rafatosta/zapzap)
|
||||
* [YouTube-Viewer](https://github.com/trizen/youtube-viewer), [Pipe Viewer](https://github.com/trizen/pipe-viewer) or [Pipeline](https://gitlab.com/schmiddi-on-mobile/pipeline) - YouTube Clients
|
||||
* [GrayJay](https://grayjay.app/desktop/) - Combines YouTube, Twitch, Rumble, etc.
|
||||
* [BetterDiscordctl](https://github.com/bb010g/betterdiscordctl) - Modded Discord Client / [Guide](https://gist.github.com/ObserverOfTime/d7e60eb9aa7fe837545c8cb77cf31172)
|
||||
* [Discover](https://github.com/trigg/Discover) - Discord Overlay
|
||||
* [dvm](https://github.com/diced/dvm) - Discord Version Manager
|
||||
|
||||
|
|
|
|||
|
|
@ -692,7 +692,6 @@
|
|||
* [Limbo](https://github.com/limboemu/limbo) or [TermOne Plus](https://termoneplus.com/) - Windows Emulator on Android / Terminal Emulators / OS Environments
|
||||
* [Ubuntu on Android](https://docs.udroid.org/) - Ubuntu Emulator / [GitHub](https://github.com/RandomCoderOrg/ubuntu-on-android)
|
||||
* [r/EmulationOnAndroid](https://www.reddit.com/r/emulationonandroid) - Android Game Emulation Subreddit
|
||||
* [Visual Novels Android](https://t.me/visual_novels_android_eng) - Android Visual Novel Ports
|
||||
* [Source Engine 4 Android](https://discord.gg/source-engine-4-android-672055862608658432) - Source Engine Ports
|
||||
* [AdrenoToolsDrivers](https://github.com/K11MCH1/AdrenoToolsDrivers) - Adreno Drivers for Android Emulators
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
* [My Cima](https://my-cima.video/) - Movies / TV
|
||||
* [ArabicFonts](https://arabicfonts.net/) or [ARFonts](https://www.arfonts.net/) - Fonts
|
||||
* [Eternal Dream Arabization](https://www.etrdream.com/) - Modern / Official / Retro Games Arabic Localization
|
||||
* [Ataraxia](https://www.ataraxia-translations.com/) - Visual Novels / Games Arabic Localization
|
||||
|
||||
## ▷ Torrenting / التورنت
|
||||
|
||||
|
|
|
|||
|
|
@ -320,7 +320,6 @@
|
|||
|
||||
## ▷ Fingerprinting / Tracking
|
||||
|
||||
* ⭐ **[CanvasBlocker](https://github.com/kkapsner/CanvasBlocker)** - Prevent Canvas Fingerprinting
|
||||
* ⭐ **[CreepJS](https://abrahamjuliot.github.io/creepjs)**, [webkay](https://webkay.robinlinus.com/), [browserrecon](https://www.computec.ch/projekte/browserrecon/?s=scan), [TZP](https://arkenfox.github.io/TZP/tzp.html), [Device Info](https://www.deviceinfo.me/), [Cover Your Tracks](https://coveryourtracks.eff.org/) or [PersonalData](https://personaldata.info/) - Tracking / Fingerprinting Tests
|
||||
* [ClearURLs](https://docs.clearurls.xyz) - Remove Tracking Elements from URLs / Can Break Sites / [GitHub](https://github.com/ClearURLs/Addon) / [GitLab](https://gitlab.com/KevinRoebert/ClearUrls)
|
||||
* [Webbkoll](https://webbkoll.5july.net/) or [Blacklight](https://themarkup.org/blacklight) - Site Tracking Info
|
||||
|
|
|
|||
BIN
docs/public/xmasfmhy.png
Normal file
BIN
docs/public/xmasfmhy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 574 KiB |
|
|
@ -141,6 +141,7 @@
|
|||
* [dotepub](https://dotepub.com/) - Convert Webpages to EBooks
|
||||
* [The Open Book](https://github.com/joeycastillo/The-Open-Book) - DIY Ebook Reader
|
||||
* [KoboCloud](https://github.com/fsantini/KoboCloud) - Sync Kobo to Cloud Services
|
||||
* [ReaderBackdrop](https://www.readerbackdrop.com/) - Wallpapers for E-Readers
|
||||
|
||||
***
|
||||
|
||||
|
|
@ -204,46 +205,39 @@
|
|||
|
||||
## ▷ Light Novels
|
||||
|
||||
* 🌐 **[Wotaku](https://wotaku.wiki/websites#novels)** - Light Novel Index / [Discord](https://discord.gg/vShRGx8ZBC)
|
||||
* 🌐 **[Wotaku](https://wotaku.wiki/websites#novels)** - Light Novel Site Index / [Discord](https://discord.gg/vShRGx8ZBC)
|
||||
* 🌐 **[EverythingMoe](https://everythingmoe.com/#section-novel)**, [2](https://everythingmoe.org/#section-novel) - Light Novel Sites Index / [Subreddit](https://www.reddit.com/r/everythingmoe/) / [Discord](https://discord.gg/GuueaDgKdS)
|
||||
* 🌐 **[The Index](https://theindex.moe/library/novels)** - Light Novel Site Index / [Discord](https://discord.gg/Snackbox) / [Wiki](https://thewiki.moe/)
|
||||
* ⭐ **[Novel Updates](https://www.novelupdates.com/)**
|
||||
* ⭐ **[NovelFire](https://novelfire.net/)**
|
||||
* [Visual Novels Android](https://t.me/visual_novels_android_eng) - Android Visual Novel Ports
|
||||
* [Just Light Novels](https://www.justlightnovels.com/) / Allows Downloads
|
||||
* [LightNovelHeaven](https://lightnovelheaven.com), [AllNovel](https://allnovel.org), [NovelFull](https://novelfull.com/) or [NOVGO](https://novgo.net/)
|
||||
* [Vynovel](https://vynovel.com/)
|
||||
* [Ranobes](https://ranobes.top/)
|
||||
* [Light Novel World](https://lightnovelworld.org/)
|
||||
* [Baka-Tsuki](https://www.baka-tsuki.org)
|
||||
* [Armaell's Library](https://armaell-library.net/) / Allows Downloads
|
||||
* [WuxiaWorld](https://www.wuxiaworld.com/)
|
||||
* [WTR-LAB](https://wtr-lab.com/) / Sign-Up Required for AI Translations
|
||||
* [WuxiaSpot](https://www.wuxiaspot.com/)
|
||||
* [WebNovel.com](https://www.webnovel.com/)
|
||||
* [AsianHobbyist](https://www.asianhobbyist.com/)
|
||||
* [Wordrain69](https://wordrain69.com/)
|
||||
* [Wuxia World](https://wuxiaworld.site/)
|
||||
* [Wuxiabox](https://www.wuxiabox.com/)
|
||||
* [NovelGo](https://novelgo.id)
|
||||
* [Wuxia.click](https://wuxia.click/)
|
||||
* [Ocean of EPUB](https://oceanofepub.net/) / Allows Downloads
|
||||
* [Novel Bin](https://novelbin.com/), [2](https://novelbin.me/)
|
||||
* [ReadNovelFull](https://readnovelfull.com)
|
||||
* [FreeWebNovel](https://freewebnovel.com/)
|
||||
* [Translated Light Novels](https://rentry.co/FMHYB64#translated-light-novels) / Allows Downloads
|
||||
* [NovelNext](https://novelnext.com/)
|
||||
* [NovelBuddy](https://novelbuddy.io/), [2](https://novelbuddy.com/)
|
||||
* [Wuxia Box](https://www.wuxiabox.com/)
|
||||
* [NovelCool](https://www.novelcool.com/)
|
||||
* [Novels.pl](https://www.novels.pl/) / Allows Downloads
|
||||
* [Scribblehub](https://www.scribblehub.com/)
|
||||
* [Novel Hall](https://www.novelhall.com/)
|
||||
* [Rekt Novel Compilations](https://rektnovelcompilations.wordpress.com/)
|
||||
* [WoopRead](https://woopread.com/)
|
||||
* [Royal Road](https://www.royalroad.com/) - Web Novels
|
||||
* [Light Novel Archive](https://t.me/LightNovelArchives) or [LN_Index](https://t.me/LN_Index) - Telegram
|
||||
* ⭐ **[NovelFire](https://novelfire.net/)** - Online Reading
|
||||
* ⭐ **[NovelCool](https://www.novelcool.com/)** - Online Reading
|
||||
* ⭐ **[WuxiaClick](https://wuxia.click/)** - Online Reading
|
||||
* [Wuxiabox](https://www.wuxiabox.com/) - Online Reading
|
||||
* [NovelNext](https://novelnext.com/) - Online Reading
|
||||
* [WuxiaSpot](https://www.wuxiaspot.com/) - Online Reading
|
||||
* [WTR-LAB](https://wtr-lab.com/) - Online Reading / Sign-Up Required for AI Translations
|
||||
* [Vynovel](https://vynovel.com/) - Online Reading
|
||||
* [Novel Bin](https://novelbin.com/), [2](https://novelbin.me/) - Online Reading
|
||||
* [Translated Light Novels](https://rentry.co/FMHYB64#translated-light-novels) - Downloads
|
||||
* [Light Novel World](https://lightnovelworld.org/) - Online Reading
|
||||
* [Wuxia World](https://wuxiaworld.site/) - Online Reading
|
||||
* [Ranobes](https://ranobes.top/) - Online Reading
|
||||
* [NovelBuddy](https://novelbuddy.io/), [2](https://novelbuddy.com/) - Online Reading
|
||||
* [ReadNovelFull](https://readnovelfull.com) - Online Reading
|
||||
* [Just Light Novels](https://www.justlightnovels.com/) - Downloads
|
||||
* [LightNovelHeaven](https://lightnovelheaven.com), [AllNovel](https://allnovel.org), [NovelFull](https://novelfull.com/) or [NOVGO](https://novgo.net/) - Online Reading
|
||||
* [NovelGo](https://novelgo.id) - Online Reading
|
||||
* [Armaell's Library](https://armaell-library.net/) - Downloads
|
||||
* [Baka-Tsuki](https://www.baka-tsuki.org) - Online Reading
|
||||
* [WuxiaWorld.com](https://www.wuxiaworld.com/) - Online Reading
|
||||
* [AsianHobbyist](https://www.asianhobbyist.com/) - Online Reading
|
||||
* [Ocean of EPUB](https://oceanofepub.net/) - Downloads
|
||||
* [Light Novel Archive](https://t.me/LightNovelArchives) or [LN_Index](https://t.me/LN_Index) - Telegram / Downloads
|
||||
* [Book Smelting Bot](https://t.me/epub_smelter_bot) - Telegram / Bot
|
||||
* [FreeWebNovel](https://freewebnovel.com/) - Web Novels
|
||||
* [WebNovel.com](https://www.webnovel.com/) - Web Novels
|
||||
* [Royal Road](https://www.royalroad.com/) - Web Novels
|
||||
* [Scribblehub](https://www.scribblehub.com/) - Web Novels
|
||||
* [Novels.pl](https://www.novels.pl/) - Novels / Audio Recordings
|
||||
* [Baka-Tsuki](https://www.baka-tsuki.org/project/?title=Main_Page) - Novels / Audio Recordings
|
||||
* [LN-Crawler-Colab](https://colab.research.google.com/github/HongYue1/LightNovel-Crawler-Colab/blob/main/lightnovel_crawler.ipynb) / [GitHub](https://github.com/HongYue1/LightNovel-Crawler-Colab), [Lightnovel Crawler](https://github.com/dipu-bd/lightnovel-crawler/), [novel-downloader](https://greasyfork.org/en/scripts/406070), [QuickNovel](https://github.com/LagradOst/QuickNovel) or [anime-dl](https://github.com/vrienstudios/anime-dl) - Light Novel Downloaders
|
||||
* [WebToEpub](https://github.com/dteviot/WebToEpub) - Novel to EPUB Converter
|
||||
|
|
@ -471,7 +465,7 @@
|
|||
* ⭐ **[Weeb Central](https://weebcentral.com/)**
|
||||
* ⭐ **[MangaDex](https://mangadex.org/)** / [Downloader](https://mangadex-dl.mansuf.link/) / [Script](https://github.com/frozenpandaman/mangadex-dl) / [Subreddit](https://www.reddit.com/r/mangadex/) / [Discord](https://discord.gg/mangadex)
|
||||
* ⭐ **[MangaPark](https://mangapark.net/)** / [Proxies](https://mangaparkmirrors.pages.dev/) / [Discord](https://discord.gg/jctSzUBWyQ)
|
||||
* ⭐ **[Comix](https://comix.to/)**
|
||||
* ⭐ **[Comix](https://comix.to/)** / [Subreddit](https://reddit.com/r/comix) / [Discord](https://discord.com/invite/kZgWWHUj22)
|
||||
* ⭐ **[MangaFire](https://mangafire.to/)** / [Subreddit](https://www.reddit.com/r/Mangafire/) / [Discord](https://discord.com/invite/KRQQKzQ6CS)
|
||||
* ⭐ **[MangaNato](https://www.manganato.gg/)**, [2](https://www.nelomanga.net/), [3](https://www.mangakakalot.gg), [4](https://www.natomanga.com/) / [Discord](https://discord.gg/Qhz84GGvE9)
|
||||
* ⭐ **[BATO.TO](https://bato.to/)**, [2](https://fto.to/) / [Mirrors](https://batotomirrors.pages.dev/) / [Discord](https://discord.com/invite/batoto)
|
||||
|
|
@ -868,6 +862,7 @@
|
|||
* ⭐ **[MyAnimeList](https://myanimelist.net/)** - Manga / Light Novels / Tracking / Database / Reviews / [Tools](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/storage#wiki_myanimelist_tools)
|
||||
* ⭐ **[Anilist](https://anilist.co/)** - Manga / Light Novels / Manhwa / Manhua / Tracking / Databsase / Reviews [Wrapper](https://github.com/AurelicButter/AniList-Node) / [Extras](https://greasyfork.org/en/scripts/370473-automail)
|
||||
* ⭐ **[MangaBaka](https://mangabaka.org/)** - Multi-Site Manga + Novel Rating Aggregator / Tracking / [Discord](https://mangabaka.dev/discord)
|
||||
* ⭐ **[Novel Updates](https://www.novelupdates.com/)** or [RanobeDB](https://ranobedb.org/) - Light Novel Tracking / Databases
|
||||
* ⭐ **[LeagueOfComicGeeks](https://leagueofcomicgeeks.com/)** - Comic Tracking / Database / Releases
|
||||
* [Listal](https://www.listal.com/) - Book Database
|
||||
* [Books Search](https://books-search.typesense.org/) - Book Database
|
||||
|
|
@ -880,7 +875,6 @@
|
|||
* [Literal](https://literal.club/) - Social Book Tracking Platform
|
||||
* [BookWyrm](https://joinbookwyrm.com/) - Book Tracking Platform / [Official Instance](https://bookwyrm.social/)
|
||||
* [MangaUpdates](https://www.mangaupdates.com/) - Manga Tracking
|
||||
* [RanobeDB](https://ranobedb.org/) - Light Novel Tracking
|
||||
* [Hardcover](https://hardcover.app/) - Tracking / Reviews / Recommendations
|
||||
* [LibraryThing](https://www.librarything.com/) - Book Cataloguing Community
|
||||
* [CandlApp](https://www.candlapp.com/) - Book Tracking / Recommendations
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@
|
|||
* [Replugged](https://replugged.dev/) - Discord Client Mod / [Discord](https://discord.gg/HnYFUhv4x4) / [GitHub](https://github.com/replugged-org/replugged)
|
||||
* [Legcord](https://legcord.app/) - Discord Client / Lightweight / [Privacy-Fork](https://github.com/Milkshiift/GoofCord) / [Discord](https://discord.gg/TnhxcqynZ2) / [GitHub](https://github.com/Legcord/Legcord)
|
||||
* [BetterDiscord](https://betterdiscord.app/) - Discord Client Mod / [Plugins](https://betterdiscord.app/plugins) / [Banned Plugins](https://rentry.co/BDBannedPlugins) / [Discord](https://discord.gg/0Tmfo5ZbORCRqbAd) / [GitHub](https://github.com/BetterDiscord/BetterDiscord)
|
||||
* [Equicord](https://equicord.org/) - Discord Client Mod / [Plugins](https://equicord.org/plugins) / [Discord](https://discord.gg/5Xh2W87egW) / [GitHub](https://github.com/Equicord/Equicord)
|
||||
* [Equicord](https://equicord.org/ or [Equibop](https://github.com/Equicord/Equibop) - Discord Client Mod / [Plugins](https://equicord.org/plugins) / [Discord](https://discord.gg/5Xh2W87egW) / [GitHub](https://github.com/Equicord/Equicord)
|
||||
* [abaddon](https://github.com/uowuo/abaddon) - Discord Client Mod / Lightweight / [Discord](https://discord.gg/wkCU3vuzG5)
|
||||
* [Discordo](https://github.com/ayn2op/discordo) - Discord Terminal Client
|
||||
* [Vesktop](https://vesktop.dev/) - Web Client w/ Vencord Preinstalled / [GitHub](https://github.com/Vencord/Vesktop)
|
||||
|
|
@ -157,7 +157,7 @@
|
|||
# ► Reddit Tools
|
||||
|
||||
* ⭐ **[Reddit Stream](https://reddit-stream.com/)** - Live Thread Viewer
|
||||
* ⭐ **[Reddit Enhancement Suite](https://redditenhancementsuite.com/)**, [Reddit++](https://greasyfork.org/en/scripts/490046), [Reddit Fix](https://greasyfork.org/en/scripts/404497-reddit-fix), [Reddit Extension](https://lawrenzo.com/p/reddit-extension), [RedditEnhancer](https://github.com/joelacus/RedditEnhancer) or [RedditMod2](https://greasyfork.org/en/scripts/29724-redditmod2) - Reddit Enhancement Extensions / Scripts
|
||||
* ⭐ **[Reddit Enhancement Suite](https://redditenhancementsuite.com/)** / [GitHub](https://github.com/honestbleeps/Reddit-Enhancement-Suite), [Reddit++](https://greasyfork.org/en/scripts/490046), [Reddit Fix](https://greasyfork.org/en/scripts/404497-reddit-fix), [Reddit Extension](https://lawrenzo.com/p/reddit-extension), [RedditEnhancer](https://github.com/joelacus/RedditEnhancer) or [RedditMod2](https://greasyfork.org/en/scripts/29724-redditmod2) - Reddit Enhancement Extensions / Scripts
|
||||
* ⭐ **[Redlib](https://github.com/redlib-org/redlib-instances/blob/main/instances.md)** / [2](https://github.com/libreddit/libreddit-instances/blob/master/instances.md), [Photon](https://photon-reddit.com/) or [RDX](https://rdx.overdevs.com/) - Reddit Frontends
|
||||
* ⭐ **[Old Reddit Redirect](https://github.com/tom-james-watson/old-reddit-redirect)** - Redirect New Reddit to Old
|
||||
* ⭐ **[Newsit](https://newsit.benwinding.com/)** - Webpage Comments Widget / [GitHub](https://github.com/benwinding/newsit)
|
||||
|
|
|
|||
|
|
@ -122,8 +122,8 @@
|
|||
|
||||
## ▷ Remote Torrenting
|
||||
|
||||
* ⭐ **[Seedr](https://www.seedr.cc/)** - 2GB / [Telegram Bot](https://t.me/TorrentSeedrBot) / [API Wrapper](https://github.com/AnjanaMadu/SeedrAPI)
|
||||
* ⭐ **[TorBox](https://torbox.app/)** - Freemium / 10GB / 10 Monthly Downloads / [Unofficial Mobile Client](https://github.com/93Pd9s8Jt/atba) / [Subreddit](https://www.reddit.com/r/TorBoxApp/) / [Discord](https://discord.com/invite/wamy) / [GitHub](https://github.com/TorBox-App)
|
||||
* ⭐ **[Seedr](https://www.seedr.cc/)** - 2GB / [Telegram Bot](https://t.me/TorrentSeedrBot) / [API Wrapper](https://github.com/AnjanaMadu/SeedrAPI)
|
||||
* [Torrent_To_Google_Drive_Downloader](https://colab.research.google.com/github/FKLC/Torrent-To-Google-Drive-Downloader/blob/master/Torrent_To_Google_Drive_Downloader.ipynb) - Google Colab
|
||||
* [webtor](https://webtor.io/) - No Limit / Download Speed Limited / No Sign-Up
|
||||
* [Multi-Up](https://multiup.io/en/upload/from-torrent) - 10 GB
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ To easily see which sites are trusted, and which are unsafe, try the **[FMHY Saf
|
|||
* TLauncher (minecraft launcher) - [Shady](https://redd.it/zmzzrt) business practices / Note that TLauncher Legacy and ATLauncher are unrelated
|
||||
* GShade (ReShade mod) - Dev added code that can trigger unwanted [reboots](https://claraiscute.neocities.org/Announcements/gshade_notice/), [2](https://claraiscute.pages.dev/Announcements/gshade_notice/)
|
||||
* TotalAV / PC Protect / Protected - Antivirus Software [Scam](https://www.malwarebytes.com/blog/detections/pup-optional-totalav) / [2](https://www.malwarebytes.com/blog/detections/pup-optional-pcprotect) / [3](https://youtu.be/PcS3EozgyhI)
|
||||
* 360 Total Security - Apps give [constant popups](https://en.wikipedia.org/wiki/Criticism_of_Qihoo_360#Malicious_promotion) to install "toolbox," which itself modifies default apps (like browser) and switches them all to 360 options. The toolbox will also be installed without consent if the repair, optimize, or clean options are used.
|
||||
* Watchug / Watchugofficial - Scammers that pretend their domain is for sale, then just block users after they've paid
|
||||
|
||||
***
|
||||
|
|
|
|||
|
|
@ -385,7 +385,7 @@
|
|||
|
||||
* ⭐ **[kdenlive](https://kdenlive.org/en/)** - Video Editor / [GitHub](https://github.com/KDE/kdenlive)
|
||||
* ⭐ **[DaVinci Resolve](https://www.blackmagicdesign.com/products/davinciresolve)** - Video Editor / Sign-Up Required / Fake Info Works / [Subtitle Generator](https://github.com/tmoroney/auto-subs) / [Discord](https://discord.gg/davinci-resolve-community-714620142096482314)
|
||||
* ⭐ **[LosslessCut](https://github.com/mifi/lossless-cut)** - Video Editor
|
||||
* ⭐ **[LosslessCut](https://github.com/mifi/lossless-cut)** - Single Video Editor
|
||||
* ⭐ **[Shotcut](https://shotcut.org/)** - Video Editor / [GitHub](https://github.com/mltframework/shotcut)
|
||||
* [Satvrn](https://rentry.co/FMHYB64#satvrn) - Video Editors / Plugins
|
||||
* [Auto-Editor](https://auto-editor.com/) - CLI Editor
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
* ⭐ **[Cineby](https://www.cineby.gd/)**, [2](https://www.bitcine.app/) or [Fmovies+](https://www.fmovies.gd/) - Movies / TV / Anime / Auto-Next / Watch Parties / [Discord](https://discord.gg/C2zGTdUbHE)
|
||||
* ⭐ **[P-Stream](https://pstream.mov/)** - Movies / TV / Anime / Auto-Next / Watch Parties / [Notes](https://github.com/fmhy/FMHY/wiki/FMHY%E2%80%90Notes.md#movie-web) / [Discord](https://discord.gg/uHU4knYRPa) / [GitHub](https://github.com/p-stream)
|
||||
* ⭐ **[XPrime](https://xprime.tv/)**, [2](https://xprime.today/) - Movies / TV / Anime / Auto-Next / Watch Parties / [Discord](https://discord.gg/pDjg5ccSgg)
|
||||
* ⭐ **[XPrime](https://xprime.stream/)**, [2](https://xprime.today/) - Movies / TV / Anime / Auto-Next / Watch Parties / [Discord](https://discord.gg/pDjg5ccSgg)
|
||||
* ⭐ **[VeloraTV](https://veloratv.ru/)** or [456movie](https://456movie.net/), [2](https://345movie.net/) - Movies / TV / Anime / Auto-Next / Watch Parties / [Discord](https://discord.gg/4SJ5c9gZUQ)
|
||||
* ⭐ **[Flixer](https://flixer.sh)**, [Hexa](https://hexa.su/) or [Vidora](https://watch.vidora.su/) - Movies / TV / Anime / Auto-Next / Watch Parties / [Discord](https://discord.com/invite/yvwWjqvzjE)
|
||||
* [Aether](https://aether.mom/), [2](https://legacy.aether.mom/) - Movies / TV / Anime / Auto-Next / Watch Parties / [Discord](https://discord.gg/MadMF7xb5q)
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
* [SpenFlix](https://watch.spencerdevs.xyz/), [2](https://spenflix.ru/) - Movies / TV / Anime / Auto-Next / Watch Parties / [Discord](https://discord.gg/RF8vMBRtTs)
|
||||
* [FilmCave](https://filmcave.ru/) - Movies / TV / Anime / Auto-Next / [Telegram](https://t.me/fmcave) / [Discord](https://discord.gg/BtpYzMbDjH)
|
||||
* [Cinema.BZ](https://cinema.bz/) - Movies / TV / Anime / Auto-Next / [Telegram](https://t.me/cinemabz)
|
||||
* [PopcornMovies](https://popcornmovies.org/) - Movies / TV / Anime / [Discord](https://discord.com/invite/JAxTMkmcpd)
|
||||
* [Cinetaro](https://cinetaro.buzz/) - Movies / TV / Anime / 3rd Party Host
|
||||
* [Smashystream](https://smashystream.com/), [2](https://flix.smashystream.xyz/), [3](https://smashystream.xyz/) - Movies / TV / Anime / [Telegram](https://telegram.me/+vekZX4KtMPtiYmRl) / [Discord](https://discord.com/invite/tcdcxrbDkE)
|
||||
* [TVids](https://www.tvids.to/), [2](https://www.tvids.net/), [3](https://watch-tvseries.net/), [4](https://tvids.me/), [5](https://tvids.tv/) - Movies / TV / Anime / Auto-Next
|
||||
|
|
@ -55,19 +56,18 @@
|
|||
* [Willow](https://willow.arlen.icu/), [2](https://salix.pages.dev/) - Movies / TV / Anime / [4K Guide](https://rentry.co/willow-guide) / [Telegram](https://t.me/+8OiKICptQwA4YTJk) / [Discord](https://discord.com/invite/gmXvwcmxWR)
|
||||
* [VoidFlix](https://voidflix.pages.dev/) or [Flixzy](https://flixzy.pages.dev/) - Movies / TV / Anime / Auto-Next / [Discord](https://discord.gg/GDfP8S243T)
|
||||
* [Cinevibe](https://cinevibe.asia/) - Movies / TV / Anime / [Discord](https://discord.com/invite/4BU2XbAPdu)
|
||||
* [Mapple.tv](https://mapple.mov/) - Movies / TV / Anime / Watch Parties / [Discord](https://discord.gg/V8XUhQb2MZ)
|
||||
* [Mapple.tv](https://mappl.tv/) - Movies / TV / Anime / Watch Parties / [Discord](https://discord.gg/V8XUhQb2MZ)
|
||||
* [HydraHD](https://hydrahd.com/), [2](https://hydrahd.ru/) - Movies / TV / Anime / Auto-Next / [Status](https://hydrahd.info/)
|
||||
* [Netplay](https://netplayz.live/) - Movies / TV / Anime / Auto-Next / [Discord](https://discord.gg/NCH4rzxJ36)
|
||||
* [TMovie](https://tmovie.tv/), [2](https://tmovie.cc) - Movies / TV / Anime / [Discord](https://discord.com/invite/R7a6yWMmfK)
|
||||
* [1PrimeShows](https://1primeshow.online/) - Movies / TV / Anime / [Discord](https://discord.gg/7JKJSbnHqf)
|
||||
* [Youflex](https://youflex.live/) - Movies / TV / Anime
|
||||
* [Flicker](https://flickermini.pages.dev/), [2](https://flickeraddon.pages.dev/) - Movies / TV / Anime / [Proxy](https://flickerminiproxy.pages.dev/) / [Note](https://github.com/fmhy/FMHY/wiki/FMHY%E2%80%90Notes.md#flicker-proxy) / [Subreddit](https://www.reddit.com/r/flickermini/)
|
||||
* [AuroraScreen](https://www.aurorascreen.org/) - Movies / TV / Anime / [Discord](https://discord.com/invite/kPUWwAQCzk)
|
||||
* [Redflix](https://redflix.co/), [2](https://redflix.club/) - Movies / TV / Anime / [Discord](https://discord.gg/wp5SkSWHW5)
|
||||
* [Cinepeace](https://cinepeace.in/) - Movies / TV / Anime / [Discord](https://discord.gg/htmB2TbK)
|
||||
* [Flixvo](https://flixvo.live/), [2](https://flixvo.fun/) - Movies / TV / Anime
|
||||
* [BoredFlix](https://www.boredflix.com/) - Movies / TV / Anime / [Discord](https://discord.gg/VHDedCcbGY)
|
||||
* [Flicker](https://flickermini.pages.dev/), [2](https://flickeraddon.pages.dev/) - Movies / TV / Anime / [Proxy](https://flickerminiproxy.pages.dev/) / [Note](https://github.com/fmhy/FMHY/wiki/FMHY%E2%80%90Notes.md#flicker-proxy) / [Subreddit](https://www.reddit.com/r/flickermini/)
|
||||
* [PopcornMovies](https://popcornmovies.org/) - Movies / TV / Anime / [Discord](https://discord.com/invite/JAxTMkmcpd)
|
||||
* [Cinema Deck](https://cinemadeck.com/), [2](https://cinemadeck.st/) - Movies / TV / Anime / [Status](https://cinemadeck.com/official-domains) / [Discord](https://discord.com/invite/tkGPsX5NTT)
|
||||
* [AlienFlix](https://alienflix.net/), [2](https://hexawatch.cc/) - Movies / TV / Anime
|
||||
* [CineBolt](https://cinebolt.net/) - Movies / TV / Anime / [Discord](https://discord.gg/7ZbCzMPt6f)
|
||||
|
|
@ -212,7 +212,7 @@
|
|||
## ▷ Anime Streaming
|
||||
|
||||
* 🌐 **[Wotaku](https://wotaku.wiki/websites)** / [Discord](https://discord.gg/vShRGx8ZBC) / [GitHub](https://github.com/wotakumoe/Wotaku), [The Index](https://theindex.moe/library/anime) / [Wiki](https://thewiki.moe/) / [Discord](https://discord.gg/Snackbox) or [EverythingMoe](https://everythingmoe.com/), [2](https://everythingmoe.org/) / [Subreddit](https://www.reddit.com/r/everythingmoe/) / [Discord](https://discord.gg/GuueaDgKdS) - Anime Site Indexes
|
||||
* ⭐ **[AnimeKai](https://animekai.to/home)**, [2](https://animekai.cc/), [3](https://animekai.ac/), [4](https://anikai.to/) or [AniGo](https://anigo.to/) - Hard Subs / Dub / Auto-Next / [Status](https://animekai.me/) / [X](https://x.com/animekai_to) / [Subreddit](https://www.reddit.com/r/AnimeKAI/)
|
||||
* ⭐ **[AnimeKai](https://animekai.to/home)**, [2](https://animekai.cc/), [3](https://animekai.ac/), [4](https://anikai.to/) or [AniGo](https://anigo.to/) - Hard Subs / Dub / Auto-Next / [Status](https://animekai.me/) / [X](https://x.com/animekai_to) / [Subreddit](https://www.reddit.com/r/AnimeKAI/) / [Discord](https://discord.gg/at5d9rkfUy)
|
||||
* ⭐ **[Miruro](https://www.miruro.com/)** - Hard Subs / Dub / Auto-Next / [Subreddit](https://www.reddit.com/r/miruro/) / [GitHub](https://github.com/Miruro-no-kuon/Miruro)
|
||||
* ⭐ **[HiAnime](https://hianime.to/)**, [2](https://hianime.nz/), [3](https://hianime.sx/), [4](https://hianime.bz/), [5](https://hianime.pe/) - Sub / Dub / Auto-Next / [Subreddit](https://reddit.com/r/HiAnimeZone/) / [Telegram](https://t.me/HiAnimeLobby) / [Discord](https://discord.gg/hianime)
|
||||
* ⭐ **HiAnime Resources** - [Official Mirrors](https://hianime.tv/) / [Enhancements](https://greasyfork.org/en/scripts/506340) / [Auto-Focus](https://greasyfork.org/en/scripts/506891)
|
||||
|
|
@ -427,7 +427,9 @@
|
|||
* ⭐ **[DaddyLive](https://dlhd.dad/)**, [2](https://dlhd.dad/), [3](https://thedaddy.dad/), [4](https://dlhd.click/), [5](https://daddylivestream.com/) - TV / Sports / [Mirrors](https://daddyny.com/)
|
||||
* ⭐ **[PPV.TO](https://ppv.to/)**, [2](https://ppvs.su/) - Live Events / [Mirrors](https://ppv.zone/) / [Discord](https://discord.gg/5AMPdpckjH)
|
||||
* ⭐ **[Sport7](https://sport7.pro/)**, [2](https://sport71.pro//) / [Player Note](https://github.com/fmhy/FMHY/wiki/FMHY%E2%80%90Notes.md#sport7) / [Telegram](https://t.me/goatifisports) / [Discord](https://discord.gg/xcdfVwgEx3)
|
||||
|
||||
* ⭐ **[Watch Footy](https://watchfooty.st/)**, [2](https://www.watchfooty.top) - Stream Aggregator / [Discord](https://discord.gg/T38kUWZHtB) / [Mirrors](https://watchfty.link/)
|
||||
|
||||
* ⭐ **[BINTV](https://bintv.fun/)** / [Discord](https://discord.gg/fMU4hpDjPg)
|
||||
* ⭐ **[SportsBite](https://sportsbite.live/)**, [2](https://sportsbite.pro/) / [Status](https://allbite.xyz/) / [Telegram](https://t.me/+Zo7CoigxqRczMjRk) / [Discord](https://discord.gg/Qg7uRXWAhU)
|
||||
* ⭐ **[SoccerStreamLinks](https://soccerstreamlinks.site/)** or [Iframely](https://iframely.biz/) - Stream Aggregator / [Discord](https://discord.gg/HwXeKNu8FU)
|
||||
|
|
@ -673,6 +675,7 @@
|
|||
* [RareDoramas](https://www.raredoramas.com/) - Rare JDrama / 480p
|
||||
* [Toku.fun](https://toku.fun/) - Japanese Superhero Movies / 360p
|
||||
* [Fanedit.org](https://fanedit.org/) - Fanedit Community / Sign-Up Required / DM Editors for Downloads
|
||||
* [HDEncode](https://hdencode.org/), [DDLBase](https://ddlbase.com/), [RapidMoviez](https://rmz.cr/) / [Mirrors](https://rmzmirrors.com/) or [rlsDB](https://rlsdb.com/) - Movie & TV DDL Forums / Requires [Debrid](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/download#wiki_.25BA_debrid_.2F_leeches)
|
||||
* [IMDb-Scout-Mod](https://greasyfork.org/en/scripts/407284) - Add Download Site Results to IMDb
|
||||
* [Video Download CSE](https://cse.google.com/cse?cx=006516753008110874046:wevn3lkn9rr) / [CSE 2](https://cse.google.com/cse?cx=89f2dfcea452fc451) / [CSE 3](https://cse.google.com/cse?cx=aab218d0aa53e3578)
|
||||
|
||||
|
|
|
|||
3787
package-lock.json
generated
3787
package-lock.json
generated
File diff suppressed because it is too large
Load diff
42
package.json
42
package.json
|
|
@ -3,7 +3,7 @@
|
|||
"packageManager": "pnpm@10.12.2+sha256.07b2396c6c99a93b75b5f9ce22be9285c3b2533c49fec51b349d44798cf56b82",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "21.7.3"
|
||||
"node": ">=25.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"api:build": "nitropack build",
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
"@fmhy/components": "^0.0.3",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
"consola": "^3.4.2",
|
||||
"feed": "^5.1.0",
|
||||
"itty-fetcher": "^1.0.10",
|
||||
|
|
@ -32,43 +32,43 @@
|
|||
"nitropack": "^2.12.9",
|
||||
"nprogress": "^0.2.0",
|
||||
"pathe": "^2.0.3",
|
||||
"reka-ui": "^2.6.0",
|
||||
"unocss": "66.5.5",
|
||||
"reka-ui": "^2.6.1",
|
||||
"unocss": "66.5.10",
|
||||
"vitepress": "^1.6.4",
|
||||
"vue": "^3.5.24",
|
||||
"vue": "^3.5.25",
|
||||
"x-satori": "^0.4.0",
|
||||
"zod": "^4.1.12"
|
||||
"zod": "^4.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20251107.0",
|
||||
"@cloudflare/workers-types": "^4.20251202.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
||||
"@iconify-json/carbon": "^1.2.14",
|
||||
"@iconify-json/fluent": "^1.2.34",
|
||||
"@iconify-json/carbon": "^1.2.15",
|
||||
"@iconify-json/fluent": "^1.2.35",
|
||||
"@iconify-json/fluent-mdl2": "^1.2.1",
|
||||
"@iconify-json/gravity-ui": "^1.2.10",
|
||||
"@iconify-json/heroicons-solid": "^1.2.1",
|
||||
"@iconify-json/logos": "^1.2.10",
|
||||
"@iconify-json/lucide": "^1.2.72",
|
||||
"@iconify-json/material-symbols": "^1.2.44",
|
||||
"@iconify-json/lucide": "^1.2.78",
|
||||
"@iconify-json/material-symbols": "^1.2.49",
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@iconify-json/qlementine-icons": "^1.2.11",
|
||||
"@iconify-json/simple-icons": "^1.2.57",
|
||||
"@iconify-json/qlementine-icons": "^1.2.12",
|
||||
"@iconify-json/simple-icons": "^1.2.61",
|
||||
"@iconify-json/twemoji": "^1.2.4",
|
||||
"@iconify/utils": "^3.0.2",
|
||||
"@types/node": "^24.10.0",
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"nitro-cloudflare-dev": "^0.2.2",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-pkgsort": "^0.2.1",
|
||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
||||
"sass": "^1.93.3",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
"sass": "^1.94.2",
|
||||
"typescript": "^5.9.3",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-auto-import": "^20.3.0",
|
||||
"vite-plugin-optimize-exclude": "^0.0.1",
|
||||
"vite-plugin-pwa": "^1.1.0",
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"vite-plugin-terminal": "^1.3.0",
|
||||
"wrangler": "^4.46.0"
|
||||
"wrangler": "^4.52.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
"cf-typegen": "wrangler types"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/vitest-pool-workers": "^0.10.5",
|
||||
"@cloudflare/vitest-pool-workers": "^0.10.13",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "~3.2.0",
|
||||
"wrangler": "^4.46.0"
|
||||
"vitest": "~4.0.15",
|
||||
"wrangler": "^4.52.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
827
pests-repellent/pnpm-lock.yaml
generated
827
pests-repellent/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
2106
pnpm-lock.yaml
generated
2106
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue