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