diff --git a/client/electron/src/components/ComposePage.tsx b/client/electron/src/components/ComposePage.tsx index f57e37e..d96d628 100644 --- a/client/electron/src/components/ComposePage.tsx +++ b/client/electron/src/components/ComposePage.tsx @@ -134,7 +134,7 @@ export default function ComposePage() { }; return ( -
+
{/* 窄屏下写信是盖在列表上的覆盖层,得有个退出口。 用 cancelCompose 而不是 showList:写信态本身要一起结束, diff --git a/client/electron/src/components/MailView.tsx b/client/electron/src/components/MailView.tsx index 09cff76..f67ea9b 100644 --- a/client/electron/src/components/MailView.tsx +++ b/client/electron/src/components/MailView.tsx @@ -406,7 +406,7 @@ function ForwardBar({ mail, onClose }: { mail: Mail; onClose: () => void }) { }; return ( -
+
@@ -978,7 +978,7 @@ function ReplyBar({ }; return ( -
+

回复 {target}

diff --git a/client/electron/src/index.css b/client/electron/src/index.css index a4ed230..4fc7a28 100644 --- a/client/electron/src/index.css +++ b/client/electron/src/index.css @@ -1172,12 +1172,45 @@ html.view-switch .glass-control, @media (prefers-reduced-motion: reduce) { html.view-switch .app-shell > *, html.view-switch .narrow-shell > *, + html.view-switch .rise-in, html.view-switch .glass-control, .animate-menu-in { animation: none !important; } } +/* + * .rise-in —— **局部**入场(写信页、回复框、转发面板)。 + * + * ★ 2026-09-15 用户:「从发信按钮到发信页面,从聊天按钮到聊天输入,以及转发拉起 + * 输入框都没有对应的动画,所有的动画都消失了,是不是我批评一下用力过猛你就 + * 全给删了」—— 他猜对了。 + * + * 9aa702b(2026-09-14)把 `html.view-switch .app-shell > *, .narrow-shell > *` + * 整条删掉、换成 `.pane-enter`,而 **`.pane-enter` 没有任何组件在穿**(已 grep 确认) + * ⇒ 这一档从「全部一起动」直接变成「一条都不动」,连这三处**局部**、 + * 有明确语义的入场也一起没了。当时的批评针对的是**整面板/整屏**一起淡入 + * (骨架跟着暗一下 = 闪),不是针对局部入场。 + * + * 所以这里只补回"刚出现的那一块":4px + 不缩放,150ms,骨架一动不动。 + * 位移与时长跟 pane-in/menu-in 同一套尺子(用户对"闪"的容忍度很低, + * 缩放与长位移是最容易读出"闪"的两项,已经拿掉了)。 + */ +@keyframes rise-in { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: none; + } +} + +html.view-switch .rise-in { + animation: rise-in 150ms cubic-bezier(0.22, 0.61, 0.36, 1) both; +} + /* * ★ 通信中栏里的直接子面板必须能被压缩(2026-09-14 用户:「通信页面的各个子页面 diff --git a/client/electron/test/narrow-layout.test.mjs b/client/electron/test/narrow-layout.test.mjs index 5c4030e..9f79188 100644 --- a/client/electron/test/narrow-layout.test.mjs +++ b/client/electron/test/narrow-layout.test.mjs @@ -242,6 +242,38 @@ check( /absolute[^`]*popup-surface/.test(addrSrc) && !/absolute[^`]*glass-control/.test(addrSrc), 'AddressInput 的菜单还穿着控件档(.glass-control) —— 那档是给贴着面板的小控件用的' ); + +/* + * 局部入场动画(2026-09-15 用户:「从发信按钮到发信页面,从聊天按钮到聊天输入, + * 以及转发拉起输入框都没有对应的动画,所有的动画都消失了」)。 + * + * 9aa702b 把 `html.view-switch .app-shell > *, .narrow-shell > *` 整条删掉、 + * 换成 `.pane-enter`,而 .pane-enter **没有任何组件在穿** ⇒ 这一档成了死规则。 + * + * 判据钉三件事:① 局部入场类存在且有独立的 keyframes;② 三处目标面真的穿了 + * 它(发信页 / 回复框 / 转发面板,去掉任一处必红);③ `prefers-reduced-motion` + * 里必须也有它 —— 否则"关动画就一点都不动"这句承诺对新增的这档不成立。 + */ +const composeSrc = code('src/components/ComposePage.tsx'); +const mailViewSrc = code('src/components/MailView.tsx'); +// 文件里有**多个** reduced-motion 块(还有一个全局的把 animation-duration 压到 0.01ms), +// 所以必须逐块看,不能只取第一个 —— 第一版就取错了块,判据自己假红。 +const reducedBlocks = css.match(/@media \(prefers-reduced-motion: reduce\)\s*\{[\s\S]*?\n\}/g) || []; +check( + '局部入场动画存在(rise-in)', + /@keyframes rise-in\s*\{/.test(css) && /html\.view-switch \.rise-in\s*\{/.test(css), + '局部入场动画不见了 —— 写信页/回复框/转发面板会"啪"地出现' +); +check( + '发信页、回复框、转发面板三处都穿 rise-in', + /rise-in/.test(composeSrc) && (mailViewSrc.match(/rise-in/g) || []).length >= 2, + '三处目标面没都穿上 rise-in(回复框与转发面板在 MailView 里,必须各有一处)' +); +check( + 'rise-in 也受 prefers-reduced-motion 管', + reducedBlocks.some(b => /view-switch \.rise-in/.test(b)), + 'reduced-motion 块里漏了 rise-in —— 关掉动画的人仍会被它位移' +); check( '淡出规则同时带 -webkit- 前缀(Electron 里以哪个为准不由我们定)', (maskRule.match(/-webkit-mask-image:/g) || []).length === 1 &&