Compare commits

...

8 commits

Author SHA1 Message Date
nbats
73e7ec2292
small fix 2025-12-05 08:59:09 -08:00
KHROTU
bbfcdfa094
update grok display link (#4385)
original script no longer updated, new ver ported from their new chrome extension
2025-12-05 08:58:12 -08:00
Pas
e697cd491f
update node and packages (#4380) 2025-12-05 08:53:07 -08:00
nbats
d673e51173
removed site 2025-12-05 08:47:38 -08:00
nbats
cd27c7234c
small fix 2025-12-05 08:42:17 -08:00
Samidy
d4d4ad0d85
Theme Handler (By Land), Christmas & Catppuccin Theme And Feedback Window Revamp (#4386)
* Add files for christmas theme, theme handler, feedback revamp and cattpuccin theme

* Add files via upload

* update image on home page

* add tree logo for faster loading

* change link to raw github
2025-12-05 08:32:54 -08:00
nbats
d0b9c2079b
starred 2 light novel sites 2025-12-05 08:17:41 -08:00
nbats
a7c32ec25b
updated 11 pages 2025-12-05 08:11:55 -08:00
37 changed files with 4987 additions and 3667 deletions

View file

@ -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)
)
}
]

View file

@ -332,4 +332,4 @@ export const sidebar: DefaultTheme.Sidebar | DefaultTheme.NavItemWithLink[] = [
}
]
}
]
]

View file

@ -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

View file

@ -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 colorSet = colors[colorName]
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]
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')
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 }
}
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;
}
.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;
if (modeColors?.brand && modeColors.brand[1] && modeColors.brand[2]) {
return {
background: `linear-gradient(135deg, ${modeColors.brand[1]} 0%, ${modeColors.brand[2]} 100%)`
}
html, body {
background-color: #ffffff !important;
}
.VPApp, .Layout, .VPContent, .VPHome, .VPHero, #app, .vp-doc {
background-color: #ffffff !important;
}
.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;
}
`
}
// Fallback to CSS var brand if present
return { background: 'var(--vp-c-brand-1)' }
}
onMounted(() => {
if (isAmoledMode.value) {
document.documentElement.classList.add('theme-amoled')
}
const generateThemeFromColor = (colorName: ColorNames): Theme => {
const colorSet = colors[colorName]
// Re-apply the theme to ensure everything is initialized
updateThemeColor(selectedColor.value, isAmoledMode.value)
})
watch([selectedColor, isAmoledMode], ([color, amoled]) => {
updateThemeColor(color, 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'
}
},
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'
}
},
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)'
}
},
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'
}
},
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)'
}
}
}
}
}
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'
:class="[
'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>

View file

@ -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 {

View file

@ -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>
<ColorPicker />
<div class="mt-4">
<ColorPicker />
</div>
<div class="mt-6 pt-6 border-t border-$vp-c-divider">
<ThemeSelector />
</div>
</div>
</template>
</template>

View 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>

View 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>

View file

@ -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

View 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; its 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 its added to `themeRegistry` and named correctly.
- Brand not changing: if a theme sets inline brand variables, ColorPicker wont 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.

View 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)'
}
}
}
}

View 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)'
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View 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)'
}
}
}
}

View 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 }

View 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'

View 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
}
}

View 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
}

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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') {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 / التورنت

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

View file

@ -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
@ -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

View file

@ -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)

View file

@ -92,10 +92,11 @@ 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
***
### [Fake Z-Lib Sites](https://www.reddit.com/r/zlibrary/wiki/index/scamsites/)
### [Fake Windows Activators](https://pastebin.com/gCmWs2GR)
### [Fake Windows Activators](https://pastebin.com/gCmWs2GR)

View file

@ -22,7 +22,8 @@
* [Cinegram](https://cinegram.net/) - Movies / TV / Anime / Auto-Next
* [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)
* [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)

3787
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -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": {

View file

@ -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"
}
}

File diff suppressed because it is too large Load diff

2106
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff