From 2b53eb976b00b1806b1206a9cba909616c626303 Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Tue, 22 Sep 2026 00:45:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=81=E4=BD=93=E4=BA=A4=E4=BA=92=E5=B1=82?= =?UTF-8?q?=EF=BC=9A=E6=8C=89=E5=8E=8B=E5=8F=8D=E9=A6=88=20+=20=E7=9C=9F?= =?UTF-8?q?=E5=8D=8A=E9=80=8F=E6=98=8E=E6=9D=90=E8=B4=A8=20+=20=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E4=BA=A4=E5=8F=89=E8=BF=87=E6=B8=A1=20+=20=E5=BC=B9?= =?UTF-8?q?=E5=B1=82=E6=9D=90=E8=B4=A8=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 apple-design(Designing Fluid Interfaces)审查落地: - 全站补 :active 按压缩放反馈(按钮/tab/链接/命令面板/前后栏导航),交互元素加 touch-action - TopBar、首页标题栏、浮动主题钮改 color-mix 半透明 + blur 真材质,配 reduced-transparency 回退 - 主题切换 250ms 交叉过渡(theme-anim 瞬时类),消除亮度突变 - CommandPalette/ShortcutsPanel 过 :scrim 淡入 + 面板自顶部锚点 scale 进入,退出原路返回 - 首页标题栏收缩由二值跳变改为滚动进度 --sq 连续插值(16–56px) - reduced-motion 例外:弹层保留短交叉淡入,禁位移与主题过渡 - 触控目标增大(主题钮 40/44px、tab、后台导航项),文章大标题负字距 + 收紧行高 --- frontend/src/admin/AdminLayout.vue | 8 ++- frontend/src/components/ThemeSwitcher.vue | 32 +++++++--- frontend/src/components/TopBar.vue | 11 +++- frontend/src/site.js | 12 +++- frontend/src/styles.css | 77 ++++++++++++++++++++++- frontend/src/views/HomeView.vue | 50 +++++++-------- frontend/src/views/PostView.vue | 4 +- 7 files changed, 150 insertions(+), 44 deletions(-) diff --git a/frontend/src/admin/AdminLayout.vue b/frontend/src/admin/AdminLayout.vue index 84559b5..87ee3e0 100644 --- a/frontend/src/admin/AdminLayout.vue +++ b/frontend/src/admin/AdminLayout.vue @@ -156,8 +156,12 @@ watch(() => route.path, () => loadCounts()) - - + + + + + +
检查登录状态…
diff --git a/frontend/src/components/ThemeSwitcher.vue b/frontend/src/components/ThemeSwitcher.vue index 9b0a05e..fafcd46 100644 --- a/frontend/src/components/ThemeSwitcher.vue +++ b/frontend/src/components/ThemeSwitcher.vue @@ -58,7 +58,12 @@ const classes = computed(() => ['theme-switcher', `theme-switcher--${props.varia font-family: inherit; line-height: 1; padding: 0; - transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease; + touch-action: manipulation; + transition: + color 0.15s ease, + background-color 0.15s ease, + border-color 0.15s ease, + transform 100ms ease-out; } .theme-switcher:hover { @@ -67,6 +72,11 @@ const classes = computed(() => ['theme-switcher', `theme-switcher--${props.varia border-color: var(--admin-accent-line); } +/* 按下瞬间就有反馈 */ +.theme-switcher:active { + transform: scale(0.92); +} + .theme-switcher .ic { font-size: 19px; line-height: 1; @@ -75,8 +85,8 @@ const classes = computed(() => ['theme-switcher', `theme-switcher--${props.varia /* ---------- inline 形态:嵌入左栏底部 ---------- */ .theme-switcher--inline { - width: 36px; - height: 36px; + width: 40px; + height: 40px; } /* ---------- floating 形态:右下角浮动 ---------- */ @@ -86,18 +96,24 @@ const classes = computed(() => ['theme-switcher', `theme-switcher--${props.varia right: 18px; bottom: 18px; z-index: 100; - width: 42px; - height: 42px; - backdrop-filter: blur(6px); + width: 44px; + height: 44px; + background: color-mix(in srgb, var(--admin-card) 78%, transparent); + backdrop-filter: blur(16px) saturate(180%); box-shadow: var(--admin-shadow); } +@media (prefers-reduced-transparency: reduce) { + .theme-switcher--floating { + background: var(--admin-card); + backdrop-filter: none; + } +} + @media (max-width: 480px) { .theme-switcher--floating { right: 12px; bottom: 12px; - width: 38px; - height: 38px; } } \ No newline at end of file diff --git a/frontend/src/components/TopBar.vue b/frontend/src/components/TopBar.vue index bd83681..04f19c7 100644 --- a/frontend/src/components/TopBar.vue +++ b/frontend/src/components/TopBar.vue @@ -29,11 +29,18 @@ import { site } from '../site' top: 0; z-index: 20; padding: 10px 18px; - background-color: var(--paper); - backdrop-filter: blur(6px); + background: color-mix(in srgb, var(--paper) 80%, transparent); + backdrop-filter: blur(10px) saturate(160%); border-bottom: 1px solid var(--line); } + @media (prefers-reduced-transparency: reduce) { + .topbar { + background: var(--paper); + backdrop-filter: none; + } + } + .brand { font-family: var(--serif); font-size: 17px; diff --git a/frontend/src/site.js b/frontend/src/site.js index 1564f37..115c498 100644 --- a/frontend/src/site.js +++ b/frontend/src/site.js @@ -64,7 +64,13 @@ export const site = reactive({ // data-theme → 前台皮肤(paper / sage / rose / ink) // data-admin-theme → 后台明暗(light / dark),跟 site.themeMode 同步 -function applyTheme() { +function applyTheme(animate = false) { + // 亮度突变是 Apple 明确点名的反模式:用户主动切换时给一次 250ms 交叉过渡 + if (animate && typeof document !== 'undefined') { + const el = document.documentElement + el.classList.add('theme-anim') + setTimeout(() => el.classList.remove('theme-anim'), 300) + } const isDark = site.themeMode === 'dark' || (site.themeMode === 'auto' && systemPrefersDark()) document.documentElement.setAttribute('data-theme', effectiveTheme(site.themeMode, site.light_skin_id)) document.documentElement.setAttribute('data-admin-theme', isDark ? 'dark' : 'light') @@ -95,7 +101,7 @@ export async function loadSite() { return site } -// 访客切模式 → 落 localStorage + 立刻应用 +// 访客切模式 → 落 localStorage + 立刻应用(带交叉过渡) watch( () => site.themeMode, () => { @@ -104,7 +110,7 @@ watch( } catch (_) { /* private mode 等场景静默 */ } - applyTheme() + applyTheme(true) } ) diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 682afb2..9cb335b 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -620,7 +620,7 @@ h4 { display: flex; align-items: center; gap: 10px; - padding: 8px 12px; + padding: 10px 12px; border-radius: 6px; font-size: 14px; color: var(--admin-ink-soft); @@ -1825,3 +1825,78 @@ body:not(.admin-shell-active) :where(a, button, input, select, textarea, [tabind .cmd-overlay { overscroll-behavior: contain; } + +/* ============================================================ + 流体交互层(apple-design 审查落地) + ============================================================ */ + +/* 按压反馈:从按下瞬间就有反应,而不是 hover 或松手 */ +button:active:not(:disabled), +a.btn:active, +a.pill:active, +.tab:active, +.tag-chip:active, +.rail-left .item:active, +.admin-side .item:active, +.cmd-panel .row:active { + transform: scale(0.97); +} + +/* 触屏:去掉浏览器对双击缩放的猜测延迟 */ +button, +a.btn, +a.pill, +.tab, +.tag-chip, +.rail-left .item, +.admin-side .item { + touch-action: manipulation; +} + +/* 主题切换:亮度突变改为一次性的 250ms 交叉过渡(仅切换瞬间挂载) */ +html.theme-anim, +html.theme-anim * { + transition: background-color 250ms ease, color 250ms ease, border-color 250ms ease !important; +} + +/* 弹层材质化:从触发源方向 scale+fade 进入,退出走同一条路径 */ +.cmd-panel { + transform-origin: top center; +} +.pop-enter-active { + transition: opacity 160ms ease-out; +} +.pop-enter-active .cmd-panel { + transition: transform 240ms cubic-bezier(0.2, 0.8, 0.25, 1), opacity 160ms ease-out; +} +.pop-enter-from { + opacity: 0; +} +.pop-enter-from .cmd-panel { + transform: scale(0.96) translateY(-10px); + opacity: 0; +} +.pop-leave-active { + transition: opacity 130ms ease-in; +} +.pop-leave-to { + opacity: 0; +} + +/* reduce ≠ 无反馈:保留短交叉淡入,去掉位移/缩放与主题过渡 */ +@media (prefers-reduced-motion: reduce) { + .pop-enter-active, + .pop-leave-active { + transition: opacity 150ms ease !important; + } + .pop-enter-active .cmd-panel { + transition: opacity 150ms ease !important; + } + .pop-enter-from .cmd-panel { + transform: none; + } + html.theme-anim, + html.theme-anim * { + transition: none !important; + } +} diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index 010aa9d..c7106f8 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -20,10 +20,13 @@ const tag = computed(() => route.query.tag || '') const page = computed(() => Number(route.query.page || 1)) const size = computed(() => site.posts_per_page || 10) -// 滚动时收缩顶部标题栏:隐藏大标题,只留 tabs 贴顶 -const scrolled = ref(false) +// 滚动收缩标题栏:16–56px 区间内连续插值 --sq(0→1), +// 反馈跟随滚动本身,而不是在阈值处二值跳变 +const headEl = ref(null) function onScroll() { - scrolled.value = window.scrollY > 24 + if (!headEl.value) return + const p = Math.min(1, Math.max(0, (window.scrollY - 16) / 40)) + headEl.value.style.setProperty('--sq', p.toFixed(3)) } onMounted(() => { onScroll() @@ -88,7 +91,7 @@ applyDocTitle('')
-
+

{{ heading }}