Files
MailUI4Agents/client/electron/src/main.tsx
JianFeeeee ababe4ae56 fix(webui): 壁纸被不透明层盖住 —— 玻璃不再层层相乘 + 浅色表面类全部接管
用户报的:「webui 目前在壁纸底上叠了太多不透明层,导致壁纸效果很差,几乎看不出来」,
追问后补充:「不只是玻璃,而是很多界面是不透明的」。两句都成立,是两个叠加的原因。

## ① 玻璃层层相乘

每层面板都吃同一个不透明度 a,两层就是 1-(1-a)²。a=0.82 时两层 0.97、三层 0.995
—— 壁纸在数学上被吃掉,而且每层各做一次 16px 模糊,图案被糊成灰块。
改法:玻璃只出现一次(外层 0.62 + 18px 模糊;嵌套层只留 0.3 色调且不再模糊;
第三层透明)。同时把遮罩 24%→12%、照片自身模糊 8→4px(那张被洗白的锅之一)。

## ② 14 个浅色表面类根本没被接管("很多界面是不透明的"就是这条)

旧规则只接管 white / gray-50 / slate-100,而源码里在用的是
gray-100(24 处)、blue-50(17)、red-50(13)、blue-100(8)、gray-200(8)、green-50(8)…
—— 全是实心的,正好盖住壁纸。

清单改为**从源码用法生成**(`scripts/gen-background-takeover.mjs` → 生成的 CSS,
构建前自动跑),因为手写清单必然烂;判据保证"源码里出现的浅色表面类必须都被接管",
并明确排除页面底(gray-50/slate-100 必须保持**全透明**,不能被改成半透明)。
按钮/徽标(*-600/700、chrome-600/700)**故意保持不透明**:小控件可读性优先
(index.css 里原有注释记着实测 4.46:1 的教训)。

## 判据

- `test/background.test.mjs` 23 条(新增 8 条):玻璃算式(两层 ≤0.85)、外层 ≤0.70、
  嵌套规则存在、**判据自检**(旧值 0.82 必须算得出 >0.95)、表面类覆盖、页面底不得进清单、
  清单非空跑。犯过一次错:第一版把判据追加在 `process.exit()` **之后** ⇒ 根本没执行,
  从"测试数没变"才发现。
- `test/manual/wallpaper-layers-verify.mjs`(真实渲染,自带无头 Chromium):
  用**纯红壁纸读绿通道**测有效不透明度(纯色下 backdrop-filter 不影响读数)。
  面板 p95:**修复前 82% → 修复后 62%**;反向对照(改回旧值)能分辨 ≥10 个百分点;
  深色照片面上面板仍是浅底;关掉壁纸的同坐标对照更实。5/5。
  过程中作废了两个指标:只看绿通道会把深色元素误判成"盖死";"面积占比"类阈值
  (Δ≥8/≥40)在 0.97 时仍会蹭过门槛,饱和而无分辨力 —— 同坐标比值才可信。

## 交付

已部署(网关内嵌 WebUI 重建):线上 CSS 已含 `--bg-glass-inner`、嵌套规则与 14 个接管类。
2026-09-14 08:09:22 +08:00

62 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { initTheme } from './stores/themeStore';
import { initBackground } from './stores/backgroundStore';
import './index.css';
// 背景开启时要变半透明的浅色表面类(由 scripts/gen-background-takeover.mjs 生成)
import './background-takeover.generated.css';;
// 必须在 render 之前:晚一步就会让深色偏好的用户看到一帧白色闪屏。
// index.html 里还有一段更早的内联脚本处理「JS bundle 到达前」那段空窗,
// 这里做的是把 store 状态与 DOM 对齐并订阅系统主题变化。
initTheme();
// 自定义背景同样要在 render 之前套用(读 localStorage + 写 CSS 变量)。
// 它只是装饰层,晚一帧不会像主题那样闪出刺眼白底,但提前套用能避免
// 「先看到纯色底、再变成背景」的抽动。
initBackground();
/**
* 用 Visual Viewport 驱动应用高度。
*
* 手机软键盘通常只缩小「可见视口」,不缩小传统 100%/100vh如果外壳还按
* 布局视口排版正文和发送按钮就会落到键盘后面。dvh 是 CSS 兜底,这个变量
* 处理 iOS WebView 与部分旧 Chromium 对动态视口更新不及时的情况。
*/
const syncVisibleViewport = () => {
const viewport = window.visualViewport;
const height = viewport?.height ?? window.innerHeight;
document.documentElement.style.setProperty('--app-height', `${Math.round(height)}px`);
};
syncVisibleViewport();
window.addEventListener('resize', syncVisibleViewport, { passive: true });
window.visualViewport?.addEventListener('resize', syncVisibleViewport, { passive: true });
window.visualViewport?.addEventListener('scroll', syncVisibleViewport, { passive: true });
// 编辑表单时隐藏窄屏底栏,把有限的键盘上方空间留给正文与操作按钮。
// focusout 要延后一帧:从一个输入框切到另一个时 activeElement 会短暂回到 body。
const syncEditingState = () => {
const el = document.activeElement;
const editing = el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;
document.documentElement.classList.toggle('form-editing', editing);
};
document.addEventListener('focusin', syncEditingState);
document.addEventListener('focusout', () => requestAnimationFrame(syncEditingState));
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
{/*
背景层挂在 App **之外**。
挂在 App 里面的话,它只会存在于主界面的那几个分支上 —— 登录页、加载页、
初始化向导都各自 return 自己的外层 div。背景是全局装饰用户不该
「一进登录页背景就没了」。它 position:fixed + z-index:-1与 App 的
布局无关,放这里最稳。
*/}
<div className="app-backdrop" aria-hidden="true" />
<App />
</React.StrictMode>
);