diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 019284713..f5a7cb5ec 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -122,7 +122,9 @@ export default defineConfig({ output: ['console', 'terminal'] }), UnoCSS({ - configFile: '../unocss.config.ts' + configFile: fileURLToPath( + new URL('../../unocss.config.ts', import.meta.url) + ) }), AutoImport({ dts: '../.cache/imports.d.ts', diff --git a/docs/.vitepress/theme/components/ThemeDropdown.vue b/docs/.vitepress/theme/components/ThemeDropdown.vue index e7f2d6994..edc480ea7 100644 --- a/docs/.vitepress/theme/components/ThemeDropdown.vue +++ b/docs/.vitepress/theme/components/ThemeDropdown.vue @@ -1,12 +1,11 @@ diff --git a/docs/.vitepress/theme/components/VPLocalSearchBox.vue b/docs/.vitepress/theme/components/VPLocalSearchBox.vue index 77bf06034..372f632e4 100644 --- a/docs/.vitepress/theme/components/VPLocalSearchBox.vue +++ b/docs/.vitepress/theme/components/VPLocalSearchBox.vue @@ -317,11 +317,16 @@ debouncedWatch( await mergeNearbyMarks() } - const excerpts = Array.from(el.value?.querySelectorAll('.result .excerpt') ?? []) + const excerpts = Array.from(el.value?.querySelectorAll('.result .excerpt') ?? []) as HTMLElement[] for (const excerpt of excerpts) { - excerpt - .querySelector('mark[data-markjs="true"]') - ?.scrollIntoView({ block: 'center' }) + const mark = excerpt.querySelector('mark[data-markjs="true"]') as HTMLElement | null + if (mark) { + const markTop = mark.offsetTop + const markHeight = mark.offsetHeight + const excerptHeight = excerpt.clientHeight + + excerpt.scrollTop = markTop - excerptHeight / 2 + markHeight / 2 + } } /** @@ -343,8 +348,10 @@ debouncedWatch( resultMarks.value = newResultMarks currentMarkIndex.value = newCurrentMarkIndex - // FIXME: without this whole page scrolls to the bottom - resultsEl.value?.firstElementChild?.scrollIntoView({ block: 'start' }) + // Reset scroll position to top + if (resultsEl.value) { + resultsEl.value.scrollTop = 0 + } }, { debounce: 200, immediate: true } ) diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 33b5ce876..379a302f3 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -38,6 +38,35 @@ export default { app.component('Feedback', Feedback) app.component('Tooltip', Tooltip) loadProgress(router) + + if (typeof window !== 'undefined') { + const originalBefore = router.onBeforeRouteChange + const originalAfter = router.onAfterRouteChanged + + router.onBeforeRouteChange = (to) => { + try { + // Force scroll-behavior: auto (instant) when changing pages (path), + // preventing the "scroll to top" animation. + // Smooth scrolling is preserved for same-page hash/anchor changes. + const targetUrl = new URL(to, window.location.href) + if (targetUrl.pathname !== window.location.pathname) { + document.documentElement.style.scrollBehavior = 'auto' + } + } catch (e) { + // Fallback if URL parsing fails + } + originalBefore?.(to) + } + + router.onAfterRouteChanged = (to) => { + originalAfter?.(to) + // Re-enable smooth scrolling shortly after navigation completes + setTimeout(() => { + document.documentElement.style.scrollBehavior = 'smooth' + }, 1) + } + } + // Initialize theme handler useThemeHandler() } diff --git a/docs/.vitepress/theme/style.scss b/docs/.vitepress/theme/style.scss index 950792f78..4c7f4db4f 100644 --- a/docs/.vitepress/theme/style.scss +++ b/docs/.vitepress/theme/style.scss @@ -1,4 +1,9 @@ +body { + transition: background-color 0.5s cubic-bezier(0.25, 0.8, 0.25, 1), color 0.5s cubic-bezier(0.25, 0.8, 0.25, 1); +} + :root { + scroll-behavior: smooth; /* Colors: Brand */ --vp-c-brand-1: theme('colors.swarm.500'); --vp-c-brand-2: theme('colors.swarm.600'); @@ -399,4 +404,52 @@ .VPLocalSearchBox .backdrop { .dark .VPLocalSearchBox .backdrop { background: rgba(0, 0, 0, 0.6); +} + +/* Glassy Tooltips */ +.v-popper--theme-tooltip .v-popper__inner { + background: var(--vp-c-bg-elv) !important; + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); + border: 1px solid var(--vp-c-divider) !important; + color: var(--vp-c-text-1) !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; +} + +.v-popper--theme-tooltip .v-popper__arrow { + display: none !important; +} + +/* Global theme styles for the dropdown to ensure correct box appearance */ +.v-popper--theme-theme-selector .v-popper__inner { + background: var(--vp-c-bg-elv); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid var(--vp-c-divider); + border-radius: 8px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); + padding: 6px; + color: var(--vp-c-text-1); +} + +.dark .v-popper--theme-theme-selector .v-popper__inner { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); +} + +.v-popper--theme-theme-selector .v-popper__arrow { + display: none; +} + +/* Override VPFlyout hover behavior when manually controlled */ +.VPFlyout.click-based-flyout:hover .menu { + opacity: 0 !important; + visibility: hidden !important; + transform: translateY(0) !important; + /* Reset transform if any */ +} + +.VPFlyout.click-based-flyout.open .menu { + opacity: 1 !important; + visibility: visible !important; + transform: translateY(0) !important; } \ No newline at end of file diff --git a/docs/.vitepress/theme/themes/themeHandler.ts b/docs/.vitepress/theme/themes/themeHandler.ts index c11c1601d..b74648d19 100644 --- a/docs/.vitepress/theme/themes/themeHandler.ts +++ b/docs/.vitepress/theme/themes/themeHandler.ts @@ -106,15 +106,21 @@ export class ThemeHandler { private applyDOMClasses(mode: DisplayMode) { const root = document.documentElement - // Remove all mode classes - root.classList.remove('dark', 'light', 'amoled') + const isDark = mode === 'dark' + const isAmoled = isDark && this.amoledEnabled.value - // Add current mode class - root.classList.add(mode) + if (isDark) { + if (!root.classList.contains('dark')) root.classList.add('dark') + if (root.classList.contains('light')) root.classList.remove('light') + } else { + if (!root.classList.contains('light')) root.classList.add('light') + if (root.classList.contains('dark')) root.classList.remove('dark') + } - // Add amoled class if enabled in dark mode - if (mode === 'dark' && this.amoledEnabled.value) { - root.classList.add('amoled') + if (isAmoled) { + if (!root.classList.contains('amoled')) root.classList.add('amoled') + } else { + if (root.classList.contains('amoled')) root.classList.remove('amoled') } } @@ -124,12 +130,12 @@ export class ThemeHandler { 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) - } - }) + // 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 @@ -296,6 +302,14 @@ export class ThemeHandler { this.setMode(newMode) } + public setAppearance(mode: DisplayMode, amoled: boolean) { + this.state.value.currentMode = mode + this.amoledEnabled.value = amoled + localStorage.setItem(STORAGE_KEY_MODE, mode) + localStorage.setItem(STORAGE_KEY_AMOLED, amoled.toString()) + this.applyTheme() + } + public setAmoledEnabled(enabled: boolean) { this.amoledEnabled.value = enabled localStorage.setItem(STORAGE_KEY_AMOLED, enabled.toString()) @@ -332,7 +346,6 @@ export class ThemeHandler { public getState() { return this.state } - public getMode() { return this.state.value.currentMode } @@ -395,6 +408,7 @@ export function useTheme() { amoledEnabled: handler.getAmoledEnabledRef(), setAmoledEnabled: (enabled: boolean) => handler.setAmoledEnabled(enabled), toggleAmoled: () => handler.toggleAmoled(), + setAppearance: (mode: DisplayMode, amoled: boolean) => handler.setAppearance(mode, amoled), state } } \ No newline at end of file diff --git a/unocss.config.ts b/unocss.config.ts index 001b1f731..2d0160f43 100644 --- a/unocss.config.ts +++ b/unocss.config.ts @@ -59,7 +59,7 @@ const createColorRules = (type: 'text' | 'bg' | 'border'): Rule[] => { export default defineConfig({ content: { - filesystem: ['.vitepress/config.mts', '.vitepress/constants.ts'] + filesystem: ['.vitepress/config.mts', '.vitepress/constants.ts', '.vitepress/shared.ts'] }, theme: { colors: {