mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-10 10:16:44 +08:00
fix(glass): align navigation spacing radii and plugin branding
This commit is contained in:
@@ -53,6 +53,7 @@ const GEOMETRY_ATTRIBUTE_FILTER = [
|
||||
'data-shell-mode',
|
||||
'data-shell-navbar-attachment',
|
||||
'data-theme',
|
||||
'data-theme-radius',
|
||||
'style',
|
||||
]
|
||||
|
||||
|
||||
@@ -350,7 +350,14 @@ onMounted(() => {
|
||||
stateObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
attributeFilter: ['class', 'style', 'data-theme', 'data-glass-appearance', 'data-glass-quality'],
|
||||
attributeFilter: [
|
||||
'class',
|
||||
'style',
|
||||
'data-theme',
|
||||
'data-theme-radius',
|
||||
'data-glass-appearance',
|
||||
'data-glass-quality',
|
||||
],
|
||||
})
|
||||
stateObserver.observe(observedShell, {
|
||||
attributes: true,
|
||||
|
||||
@@ -161,6 +161,7 @@ describe('GlassFixedShellBackplate', () => {
|
||||
for (const wrapper of mountedWrappers.splice(0)) wrapper.unmount()
|
||||
document.querySelectorAll('.layout-wrapper').forEach(element => element.remove())
|
||||
document.documentElement.removeAttribute('data-theme')
|
||||
document.documentElement.removeAttribute('data-theme-radius')
|
||||
vi.restoreAllMocks()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
@@ -265,6 +266,21 @@ describe('GlassFixedShellBackplate', () => {
|
||||
expect(wrapper.findAll('clipPath rect')).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('updates the shared clip when theme radius changes without resizing navigation', async () => {
|
||||
const { wrapper, sidebar } = mountConnectedShell()
|
||||
await settleGeometry()
|
||||
const style = window.getComputedStyle(sidebar)
|
||||
vi.mocked(window.getComputedStyle).mockReturnValue({ ...style, borderTopLeftRadius: '24px' })
|
||||
|
||||
document.documentElement.dataset.themeRadius = 'extra'
|
||||
await settleGeometry()
|
||||
|
||||
for (const rect of wrapper.findAll('clipPath rect')) {
|
||||
expect(Number(rect.attributes('rx'))).toBeCloseTo(24 / backplateRect.width, 8)
|
||||
expect(Number(rect.attributes('ry'))).toBeCloseTo(24 / backplateRect.height, 8)
|
||||
}
|
||||
})
|
||||
|
||||
it('refreshes dimensions and disconnects the resize observer on unmount', async () => {
|
||||
const { wrapper } = mountConnectedShell()
|
||||
await settleGeometry()
|
||||
|
||||
@@ -107,6 +107,7 @@ describe('GlassNavbarRefractionDefs', () => {
|
||||
wrapper?.unmount()
|
||||
wrapper = undefined
|
||||
shell.remove()
|
||||
delete document.documentElement.dataset.themeRadius
|
||||
vi.restoreAllMocks()
|
||||
vi.unstubAllGlobals()
|
||||
vi.useRealTimers()
|
||||
@@ -420,6 +421,21 @@ describe('GlassNavbarRefractionDefs', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('regenerates the optical outline when the theme radius attribute changes', async () => {
|
||||
wrapper = mount(GlassNavbarRefractionDefs)
|
||||
await settle()
|
||||
|
||||
radius = 24
|
||||
document.documentElement.dataset.themeRadius = 'extra'
|
||||
await flushPromises()
|
||||
await settle()
|
||||
|
||||
expect(createGlassNavbarDisplacementMap).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ width: 1423, height: 64, radius: 24 }),
|
||||
)
|
||||
expect(shell.dataset.glassNavbarRefractionReady).toBe('true')
|
||||
})
|
||||
|
||||
it('does not retry a failed geometry in a feedback loop', async () => {
|
||||
wrapper = mount(GlassNavbarRefractionDefs)
|
||||
await vi.advanceTimersByTimeAsync(65)
|
||||
|
||||
@@ -4,6 +4,26 @@ import { cwd } from 'node:process'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
describe('glass overlay material styles', () => {
|
||||
it('reserves space below detached desktop navigation and follows the compact theme radius', () => {
|
||||
const styles = readFileSync(resolve(cwd(), 'src/styles/themes/_glass-v3.scss'), 'utf8')
|
||||
|
||||
expect(styles).toContain('--glass-v3-navigation-radius: var(--app-field-radius, 16px)')
|
||||
expect(styles).toContain('--glass-v3-navigation-content-gap: 16px')
|
||||
expect(styles).toContain('border-radius: var(--glass-v3-navigation-radius)')
|
||||
expect(styles).toContain('--shell-floating-navbar-radius: var(--glass-v3-navigation-radius)')
|
||||
expect(styles).toMatch(/var\(--layout-navbar-block-size\)\s*-\s*var\(--navbar-tab-height, 0px\)/u)
|
||||
expect(styles).toContain('var(--glass-v3-navigation-content-gap)')
|
||||
expect(styles).toContain('.layout-window-controls-overlay-shell')
|
||||
expect(styles).not.toContain('border-radius: 16px')
|
||||
})
|
||||
|
||||
it('keeps plugin logo tinting on the material formulas and displays complete logos', () => {
|
||||
const styles = readFileSync(resolve(cwd(), 'src/styles/themes/_glass-v3.scss'), 'utf8')
|
||||
|
||||
expect(styles).not.toMatch(/--plugin-card-banner-(?:tint|scrim)\s*:/u)
|
||||
expect(styles).toMatch(/\.plugin-card__plugin-icon \.v-img__img\s*\{\s*object-fit:\s*contain;/u)
|
||||
})
|
||||
|
||||
it('keeps overlays translucent enough for CSS backdrop compositing in every material', () => {
|
||||
const styles = readFileSync(resolve(cwd(), 'src/styles/themes/glass.scss'), 'utf8')
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
--glass-v3-sheen: clamp(0.04, calc(0.035 + var(--glass-reflection, 0.38) * 0.18), 0.2);
|
||||
--glass-v3-shadow: 0 12px 30px rgba(0, 0, 0, 0.14);
|
||||
--glass-v3-card-tint: 0;
|
||||
// 导航使用随主题递增的紧凑半径档,比大块内容表面略收敛。
|
||||
--glass-v3-navigation-radius: var(--app-field-radius, 16px);
|
||||
--glass-v3-navigation-inset: 8px;
|
||||
--glass-v3-navigation-content-gap: 16px;
|
||||
--glass-v3-navigation-blur: clamp(
|
||||
0.65px,
|
||||
calc(0.9px + var(--glass-surface-density, 0.62) * 0.6px - var(--glass-background-visibility, 0.58) * 0.25px),
|
||||
@@ -122,13 +126,7 @@
|
||||
}
|
||||
|
||||
.layout-page-content .plugin-card__banner {
|
||||
--plugin-card-banner-scrim: linear-gradient(rgba(23, 27, 32, 0.035), rgba(23, 27, 32, 0.1));
|
||||
--plugin-card-banner-tint: linear-gradient(
|
||||
125deg,
|
||||
rgba(var(--plugin-card-effective-accent-rgb), 0.16),
|
||||
rgba(var(--plugin-card-effective-accent-rgb), 0.045) 70%
|
||||
);
|
||||
|
||||
// 品牌染色与吸收继续由各材质公式决定,轮廓只补充轻量的透光边缘。
|
||||
border-block-end: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
@@ -137,12 +135,18 @@
|
||||
}
|
||||
|
||||
.layout-page-content .plugin-card__plugin-icon {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--app-control-radius, 14px);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.25),
|
||||
0 5px 12px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.layout-page-content .plugin-card__plugin-icon .v-img__img {
|
||||
object-fit: contain;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.layout-page-content .media-card > .v-card-text {
|
||||
// 电影画面仍可检查,文字所在的下半区才承担阅读吸收。
|
||||
background: linear-gradient(
|
||||
@@ -169,26 +173,34 @@
|
||||
}
|
||||
|
||||
.layout-vertical-nav {
|
||||
border-radius: 16px;
|
||||
border-radius: var(--glass-v3-navigation-radius);
|
||||
box-shadow: var(--glass-v3-shadow) !important;
|
||||
block-size: calc(100% - 16px);
|
||||
inline-size: calc(var(--glass-v3-nav-reserved-width) - 8px) !important;
|
||||
inset-block-start: 8px;
|
||||
inset-inline-start: 8px;
|
||||
block-size: calc(100% - 2 * var(--glass-v3-navigation-inset));
|
||||
inline-size: calc(var(--glass-v3-nav-reserved-width) - var(--glass-v3-navigation-inset)) !important;
|
||||
inset-block-start: var(--glass-v3-navigation-inset);
|
||||
inset-inline-start: var(--glass-v3-navigation-inset);
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
&.layout-vertical-nav-collapsed .layout-vertical-nav.hovered {
|
||||
inline-size: calc(#{variables.$layout-vertical-nav-width} - 8px) !important;
|
||||
inline-size: calc(#{variables.$layout-vertical-nav-width} - var(--glass-v3-navigation-inset)) !important;
|
||||
}
|
||||
|
||||
.layout-navbar {
|
||||
border-radius: 16px !important;
|
||||
inline-size: calc(100% - var(--glass-v3-nav-reserved-width) - 16px) !important;
|
||||
inset-block-start: 8px !important;
|
||||
inset-inline-start: calc(var(--glass-v3-nav-reserved-width) + 8px) !important;
|
||||
border-radius: var(--glass-v3-navigation-radius) !important;
|
||||
inline-size: calc(100% - var(--glass-v3-nav-reserved-width) - 2 * var(--glass-v3-navigation-inset)) !important;
|
||||
inset-block-start: var(--glass-v3-navigation-inset) !important;
|
||||
inset-inline-start: calc(var(--glass-v3-nav-reserved-width) + var(--glass-v3-navigation-inset)) !important;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
// 标签栏由内容层另行预留;这里只补主导航内缩和下方间距,避免标签高度重复占位。
|
||||
.layout-page-content {
|
||||
padding-block-start: calc(
|
||||
var(--layout-navbar-block-size) - var(--navbar-tab-height, 0px) + var(--glass-v3-navigation-inset) +
|
||||
var(--glass-v3-navigation-content-gap)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
@@ -272,6 +284,8 @@
|
||||
)
|
||||
body[data-theme='glass']
|
||||
.layout-wrapper.layout-navbar-floating-eligible {
|
||||
--shell-floating-navbar-radius: var(--glass-v3-navigation-radius);
|
||||
|
||||
.layout-navbar,
|
||||
.navbar-content-container {
|
||||
transition-property:
|
||||
|
||||
@@ -79,6 +79,10 @@ describe('createGlassNavbarDisplacementField', () => {
|
||||
{ width: 68, height: 862, radius: 0 },
|
||||
{ width: 252, height: 846, radius: 16 },
|
||||
{ width: 60, height: 846, radius: 16 },
|
||||
{ width: 252, height: 846, radius: 8 },
|
||||
{ width: 252, height: 846, radius: 24 },
|
||||
{ width: 60, height: 846, radius: 8 },
|
||||
{ width: 60, height: 846, radius: 24 },
|
||||
])('keeps two-dimensional sampling forward and inside the image for $width x $height r$radius', geometry => {
|
||||
for (const deformation of [0, 48, 100])
|
||||
for (const translation of [0, 48, 100])
|
||||
|
||||
Reference in New Issue
Block a user