feat(webui): 导航合并 + 悬浮玻璃 + 页面动画 + 控件档透明度 + 圆角无条件生效

用户四封信(同一线索)提的六件事,都在这里:

## ① 导航合并(「导航栏的内容有点多了」)

收件箱/发件箱/授权 → 一项「通信」,进去由**内部页签**区分(CommTabs);
新建 → 「通信」页内**悬浮圆形加号**(ComposeFab);导航只剩 通信/日历/联系人;
管理 → 合进「我的」,管理员在页面**最下面**看得到(非管理员不渲染,不是禁用)。

`viewMode` 仍是原来那三个值(几十处调用点不用改),新增 `commTab` 记住上次用的子页签;
通信项的徽标 = 未读 + 待决策(授权信息不能因为合并而消失)。

## ② 窄屏底部导航:悬浮玻璃(「还是固定底部延伸,没有玻璃效果,也没有悬浮」)

原来是 `border-t bg-chrome-900` 通栏贴底。改成:左右/离底各留 10px、圆角、
深色半透明 + `blur(18px)`、投影,安全区并入下外边距。

## ③ 页面动画(「页面极度缺少动画,所有页面都是直接出现」)

在 `<html>` 上挂一个短命类触发外壳直接子面板的入场动画。**没有用 key 重挂载**:
面板是 flex 链上的一环,套 wrapper 会改掉 flex 传递(窄屏覆盖层最先坏)。
并且尊重 `prefers-reduced-motion`(关了就一点都不动)。

## ④ 控件档透明度(「复选框和地址猜测项…他们才是真正需要拉低透明度的地方」)

新增第三档 `--bg-glass-control`(0.5/0.55):授权多选胶囊、地址建议菜单。
三档递减:正文面 0.88 > 嵌套 0.82 > 控件 0.5。

## ⑤ 打底 vs 透明(上一封「该打底的地方透了、该透的空白处糊了」)

正文面回到 0.88(读得清优先)、空白/页面底透明**且不模糊**、面板模糊降到 10px。

## ⑥ 圆角与玻璃**无条件**生效(「大面积缺失圆角与玻璃效果…都是硬截断」)

根因:`.app-shell` 的留缝/圆角/投影原先全写在 `html[data-bg='on']` 里
⇒ **没开壁纸的账号**看到的是硬边不透明面板。现在几何下沉为无条件基线,
壁纸相关的加强仍叠加在上面。窄屏内容面板同样浮起来。

## 判据

- `test/nav-merge.test.mjs` 8 条(含自检:重构前的写法必须判红)——
  改完跑老套件 254 条**全绿却一条都没测导航结构**,结构改动必须自带判据。
- `test/manual/nav-restructure-verify.mjs`:真浏览器 9 条(导航项数、页签切换真的换内容、
  加号 56×56 且 `elementFromPoint` 命中自己、我的页底部管理入口在退出登录之后、
  控件档 α=0.5 对正文面 α=0.88 的反向对照)。
- `test/manual/narrow-glass-verify.mjs` 6 条:悬浮几何(三边留缝 10px、圆角 14px、
  缝隙处命中的是页面底 ⇒ 真的浮着)、玻璃(α=0.82 + blur18)、触摸高度最小 49px、
  动画真的在跑(静止时 `animationName=none`,切换后 =pane-in)且 reduced-motion 下不动。
- `test/background.test.mjs`:把三条**过时判据**(早前那版"越透越好")按现行契约重写 ——
  旧判据留着只会把我拽回错误方向。30 条全绿。

## 顺带

窄屏探针原先只调视口、**没模拟触屏**,于是 `(hover: hover)` 仍为真,
把「悬停才显形的次要动作」误报成透明 —— 差点去"修"一个本来就对的规则。
已改成 hasTouch + isMobile + DPR 的触屏上下文,且收尾只关自己的 context
(CDP 连的是共享 Chromium,`browser.close()` 会把别人的标签页一起关掉)。
This commit is contained in:
2026-09-14 10:35:05 +08:00
parent 552fbc731e
commit 76ce201c3a
15 changed files with 911 additions and 107 deletions

View File

@ -169,12 +169,29 @@ check('新组件未使用未映射色族', unmapped.length === 0, unmapped.join(
for (let i = 1; i < layers; i++) opaque = opaque + nested * (1 - opaque);
return opaque;
};
const ctrl = Number((css.match(/--bg-glass-control:\s*([\d.]+)/) || [])[1]);
const stacked = composite(glass, inner, 2);
check('玻璃有"嵌套层"变量', Number.isFinite(glass) && Number.isFinite(inner), `glass=${glass} inner=${inner}`);
check('有"嵌套面板不再各叠一次"的规则', /\.bg-white \.bg-white \{/.test(css));
check('两层嵌套后的有效不透明度 ≤ 0.85', stacked <= 0.85, `实际 ${stacked.toFixed(3)}`);
check('外层玻璃 ≤ 0.70', glass <= 0.7, `实际 ${glass}`);
check('判据自检:旧值 0.82 层层叠必须算得出 >0.95', composite(0.82, 0.82, 2) > 0.95);
/*
* ★ 这三条 2026-09-14 重写(原先断言的是"越透越好",方向是错的)。
*
* 我第一版按"内容更通透"把正文面压到 0.45,用户当场指出:「你把大量需要打底的
* 场景(弹窗正文等)改为了透明。真正该透明的地方(空白区域)加了很浓的模糊」。
* 于是判据改成**两类面分开**
* - 承载文字的面必须够实0.80.95),否则字压在壁纸上读不动;
* - 空白/页面底:透明且不模糊(另有用例)。
* 旧的"≤0.70 / ≤0.85"留着只会把我再拽回那个错误方向,所以连同理由一起改掉。
*/
check('正文面够实0.800.95,读得清优先)', glass >= 0.8 && glass <= 0.95, `实际 ${glass}`);
check('嵌套面比外层透、但仍打底0.70–外层)', inner >= 0.7 && inner <= glass, `glass=${glass} inner=${inner}`);
check('两层嵌套后仍接近实心≥0.9', composite(glass, inner, 2) >= 0.9, `实际 ${stacked.toFixed(3)}`);
check(
'控件档最透,且比嵌套面更透(复选框/地址建议)',
ctrl <= 0.6 && ctrl < inner,
`control=${ctrl} inner=${inner}`
);
check('判据自检:两档一样透是分不出层次的(必须算得出 ≥0.9', composite(0.82, 0.82, 2) >= 0.9);
// 17) ★ 浅色表面类的接管清单必须跟着源码走。
//
@ -210,11 +227,21 @@ check('新组件未使用未映射色族', unmapped.length === 0, unmapped.join(
const navNoBlur = /html\[data-bg='on'\] \.app-shell > \.bg-chrome-900 \{[\s\S]{0,160}backdrop-filter: none/.test(css);
check('导航栏在背景模式下完全不透明', opaqueNav);
check('导航栏不再参与模糊', navNoBlur);
check('内容面板更通透(--bg-glass ≤ 0.55', glass <= 0.55, `实际 ${glass}`);
check('外壳留缝(浮动面板布局)',
/html\[data-bg='on'\] \.app-shell \{[\s\S]{0,80}padding: var\(--pane-gap\)/.test(css));
check('顶层面板圆角化',
/html\[data-bg='on'\] \.app-shell > \* \{[\s\S]{0,140}border-radius: var\(--radius-card\)/.test(css));
/*
* ★ 浮动面板几何必须**无条件**生效2026-09-14 用户:「通信页面大面积缺失圆角与
* 玻璃效果,所有有内容与无内容区域都是硬截断」)。
*
* 根因:这些规则原先全写在 `html[data-bg='on']` 里 ⇒ 没开壁纸的账号看到硬边面板。
* 所以判据不能再带 data-bg 前缀 —— 带前缀等于把缺陷写进判据。
*/
check('外壳留缝与圆角是无条件的(不只壁纸模式)',
/(?<!data-bg='on'\] )\.app-shell \{[\s\S]{0,80}padding: var\(--pane-gap\)/.test(css) &&
/(?<!data-bg='on'\] )\.app-shell > \* \{[\s\S]{0,160}border-radius: var\(--radius-card\)/.test(css));
check('窄屏内容面板也浮起来(圆角)',
/\.narrow-shell > \*:not\(\.narrow-nav\) \{[\s\S]{0,80}border-radius: var\(--radius-card\)/.test(css));
check('窄屏底部导航是悬浮玻璃(留缝 + 模糊)',
/\.narrow-nav \{[\s\S]{0,220}backdrop-filter: blur\(/.test(css) &&
/\.narrow-nav \{[\s\S]{0,120}margin: 0 10px/.test(css));
console.log(`\n背景:${pass} 通过${fail ? `${fail} 失败` : ''}`);
process.exit(fail ? 1 : 0);