关于 dark-mode-maker
用于为 HTML 页面添加明暗主题切换的技能,包含圆形揭示动画、中性暗色调色板和防止 FOUC 的脚本。它还要求通过 Playwright 验证动画位置、主题提交时机及减少动态效果分支。
主要功能
- 01自动插入主题切换控件
- 02使用圆形揭示与边缘光效
- 03以 localStorage 主题值防止 FOUC
- 04采用中性暗色调色板
- 05提供 Playwright 动画验收脚本
- 06支持减少动态效果的过渡
适用场景
- 01为 HTML 页面添加明暗主题开关
- 02按点击位置触发主题切换揭示动画
- 03验证覆盖层位置及主题提交时序
- 04检查减少动态效果时的切换行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rheinmir/ setup --skill dark-mode-maker安装后在 Claude Code 中输入/dark-mode-maker确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
scripts/package-lock.jsonpackage.jsonverify-theme-motion.mjs详细介绍
技能:dark-mode-maker
从 docs-site-macos 中独立拆分出来的模块(反馈 160926:“把实现 dark/light mode 效果的整个东西拿出来,做成一个独立技能”)。适用于任何需要 dark/light 切换按钮的 HTML 页面,不仅限于 docs-site-macos。
WHAT
目的和上下文
- **目的:**为 HTML 页面添加 dark/light 切换按钮,点击后从光标位置向外扩散 circle-reveal(限制在按钮边界内),并带有 crest-glow;采用符合市场惯例的中性深色调色板,防止 FOUC,并使用 Playwright 进行真实验收。
- 触发条件(何时使用):
- 页面需要 dark/light 切换按钮(按照
html-theme-toggle-required规则,所有面向用户的 HTML 页面都需要)。 - 用户说“做 dark mode”“theme toggle”“切换亮色和暗色”“circle reveal”“界面切换效果”。
docs-site-macos§ Theme Toggle 指向此处,而不是重新嵌入全部 CSS/JS。
- 页面需要 dark/light 切换按钮(按照
- 非目标:
- 不用于判断页面是否必须提供 toggle——这是
html-theme-toggle-required(/fdk§ Rules)的规则;本技能只负责在决定提供 toggle 后处理“如何切换”。 - 不是文档站点框架(那是
docs-site-macos)。
- 不用于判断页面是否必须提供 toggle——这是
心智模型
<nav> 已存在 → JS 插入 .theme-row > .lbl + .theme-switch → 点击(将 clientX/Y 限制在按钮边界内)→ overlay .theme-reveal(目标模式的背景)通过 clip-path circle 扩散,并在其自身添加 crest drop-shadow → 覆盖整个页面 → 提交 data-theme + localStorage → 淡出 → 移除 overlay。启用 reduced-motion 时 → 使用 ≤150ms 的交叉淡化,不使用空间动画。
输入和输出约定
| 字段 | 必填? | 含义 | |
|---|---|---|---|
| 输入 | 含有 <nav>(或同等容器)且包含导航链接的 HTML 页面 | 是 | JS 会自动将切换按钮插入末尾 |
| 输入 | <页面名称>(localStorage 键)+ 页面实际的 light/dark 背景色 | 是 | 填入 FOUC 代码片段和 JS(§4) |
| 输入 | 没有侧边栏的页面 | 否 | 放在页面 footer 同一行,仍使用带标签的切换按钮 |
| 输出 | 页面内的 CSS switch + FOUC 脚本 + JS reveal + 中性深色调色板 | 是 | 保留 class 名 .theme-row/.theme-switch/.theme-reveal |
| 输出 | verify-theme-motion.mjs 的结果 | 是 | 断言 overlay 的坐标和颜色正确、覆盖后才提交、移除 DOM,以及 reduced-motion 分支 |
规则和能力
- RULE-01 (MUST):不要为涟漪效果单独创建辅助元素——在
.theme-reveal本身使用多层filter:drop-shadow(见 §4;已测得单独创建元素会导致延迟和同步偏差)。 - RULE-02 (MUST):不要在
.theme-row上使用backdrop-filter(见 §1),也不要在 dark-mode 代码块中单独覆盖其background。 - RULE-03 (MUST):扩散起点始终限制在按钮边界内(
.theme-switch的getBoundingClientRect())——无论点击时光标位于屏幕何处,都不能扩散到按钮外。 - RULE-04 (MUST):采用中性 dark-mode 调色板(§3)——平台背景、边框和文字中不得加入高饱和度颜色;accent 始终只保留一种颜色。
- RULE-05 (MUST):
docs-site-macos(以及其他所有需要 toggle 的 HTML 生成技能)都应指向本技能,而不是重新嵌入 CSS/JS——保持单一来源,修改一处即可。 - 能力:修改目标 HTML 页面的 CSS/JS/markup;临时安装 headless 浏览器依赖并运行验收脚本。
失败边界
- 页面没有
<nav>/容器 → 脚本提前return,不会出现 toggle → 澄清应在哪个容器(或页面 footer 行)放置按钮。 - 验收脚本报错(overlay 坐标/颜色错误、覆盖前就提交、overlay 未移除、reduced-motion 下仍有
clip-path)→ 失败,修复后重新运行。 - 用户仍觉得慢,即使已缩短 duration → 澄清/切换到 instant 分支(v5),不要一味降低 TOTAL_MS。
- 关于“页面是否必须提供 toggle”的问题 → 超出范围,遵循
html-theme-toggle-required规则。
HOW
主工作流
| 步骤 | 类型 | 输入 | 操作 | 输出/退出 | 失败/后续 |
|---|---|---|---|---|---|
| W01 | 确定性 | 页面 | 检查标记契约:是否有包含链接的 <nav> | 已确定容器 | 没有 → B02 |
| W02 | 效果 | 页面 | §1 开关:CSS .theme-row sticky,background:transparent,无 backdrop-filter | 开关 CSS | — |
| W03 | 效果 | <tên-trang> | §2 防止 FOUC:脚本读取 localStorage,提前设置 data-theme | 不闪烁 | — |
| W04 | 效果 | 页面 | §3 中性深色调色板(token 表) | 深色 token | — |
| W05 | 效果 | 实际背景色 | §4 圆形揭示 + crest-glow:CSS .theme-reveal + JS(更改 <tên-trang>、2 种颜色 + 2 种 tint) | 切换正常运行 | 用户要求即时切换 → B03 |
| W06 | 确定性 | HTML 文件 | 验收:node skills/dark-mode-maker/scripts/verify-theme-motion.mjs <file.html> | PASS | FAIL → 修复,重复 W06 |
各步骤详情(W01–W06 的事实来源):
契约(最小标记)
需要一个包含导航链接的 <nav>(或等效容器)——开关会由 JS 自动插入末尾,无需手动编写标记:
<nav>…link điều hướng…</nav>
JS 会自动创建 .theme-row > .lbl + .theme-switch,将 data-theme 设置到 <html> 上,并保存到 localStorage。不要更改这些 class 名称——验收脚本(scripts/verify-theme-motion.mjs)会依赖它们。
1. 开关(switch)
页脚行在父容器底部粘性固定(position:sticky),左侧为标签,右侧为开关,上方有一道细分隔线——不要在角落散布图标胶囊:
:root{--nav-pad-y:18px} /* nguồn chân lý duy nhất cho padding dọc của container cha */
.theme-row{position:sticky;bottom:calc(-1 * var(--nav-pad-y));margin-top:auto;display:flex;
align-items:center;justify-content:space-between;padding:11px 16px;
border-top:1px solid rgba(30,90,170,.14);background:transparent}
.theme-switch .track{display:inline-block;position:relative;width:50px;height:26px;border-radius:999px;…}
.theme-switch .track::before{content:'☀️';left:6px;…} .theme-switch .track::after{content:'🌙';right:6px;…}
.theme-switch .knob{position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff;transition:left .18s}
.theme-switch.on .knob{left:26px} .theme-switch.on .track{background:…dark…}
⚠️ .theme-row 必须设置 background:transparent,不得单独设置 backdrop-filter(160926 的教训,已实测两轮错误)。 .theme-row 是父容器(nav)的直接子元素,不是独立的 content-card:
- **错误做法 1——单独填充背景:**给它单独设置纯色背景(即使是“看起来差不多”的 nav 渐变),也会显露为一块与后方玻璃层分离的区域——实际反馈:“这个 div 就一直保持透明吧”。
- **错误做法 2——
background:inherit:**以为已经修好,但inherit只会复制渐变的公式,然后在.theme-row自己的小盒子上重新绘制——并不会露出父级已绘制的准确像素,色调仍然不一致。 - **错误做法 3——为了“保险”保留
backdrop-filter:blur:**即使背景确实透明(测得rgba(0,0,0,0)),额外叠加的一层模糊会覆盖在父级已自行模糊的区域上,形成“二次磨砂玻璃”条带,看起来像独立的区块,尽管没有颜色——这是通过实际截图发现的,并非猜测。 - 正确做法:
.theme-row设置background:transparent,不设置backdrop-filter。直接露出父级已渲染的像素,不要再自行绘制或模糊任何内容。
2. 防止 FOUC
<script>(function(){try{var t=localStorage.getItem("<tên-trang>-theme");
if(t==="dark"||t==="light")document.documentElement.setAttribute("data-theme",t)}catch(e){}})();</script>
3. 暗色模式调色板——市场主流的中性色调,不带藏青色(160926 经验)
初版使用背景色 #0c0f16 + 边框 rgba(120,160,220,.18)(深藏青色)——实际反馈:“暗色模式的配色……还是糟透了,按市场主流重新选一套颜色吧”。参考了目前评价最高的 3 款暗色模式产品(GitHub Dark、Vercel/Geist、Linear):背景采用接近黑色的中性色(不偏蓝),边框采用透明白色(不使用饱和色边框),只保留一种强调色(此处为 Apple 系统蓝 #0a84ff)作为点缀——不要在界面元素和边框中铺开使用饱和色:
| Token | 之前(偏藏青色,错误) | 之后(中性,市场主流) |
|---|---|---|
| 基础背景 | #0c0f16 | #09090b |
| 背景渐变,stop 2 | #0a0d13 / #0d1017 | #08080a |
--glass2(卡片) | rgba(30,34,44,.72) | rgba(32,32,36,.72) |
--glass3(表格/数据) | rgba(36,40,52,.9) | rgba(39,39,44,.9) |
--border | rgba(120,160,220,.18)(饱和蓝色) | rgba(255,255,255,.10)(透明白色——GitHub/Vercel/Linear 在深色背景上绘制边框的正确方式) |
--t1(正文) | #e7e9ee / #e8eaf0 | #f4f4f5 |
--t2(辅助文字) | #9aa2b1(蓝灰色) | #a1a1aa(中性灰) |
| 导航/父容器 | rgba(18,21,28,.82) 或渐变 rgba(24,30,44,…) | rgba(20,20,23,.82) 或渐变 rgba(28,28,31,…) |
| 切换开关轨道(开启) | #2b3040→#191d27 / rgba(60,70,110,.55) | #2c2c30→#19191b / rgba(58,58,64,.6) |
| 模式切换覆盖层背景色 | #0c0f16 | #09090b |
强调色(#0a84ff,Apple 次要标签中的 6 色之一)保持不变——问题不在于它“糟透了”,它是刻意设计的点缀。需要调整为中性色的只有背景/边框/文字。
4. Circle-reveal + crest-glow(必需)——从光标位置扩散,并限制在按钮边界内
扩散起点:点击时鼠标光标的位置,并限制(clamp)在按钮边界内(160926 经验——之前从按钮中心扩散,位置固定):使用实际点击坐标(e.clientX/clientY),并将其限制在按钮的 getBoundingClientRect() 范围内——点击按钮左侧或右侧边缘时,扩散起点应跟随对应边缘,不得跑出按钮。按 Enter/Space 键时(没有鼠标坐标),则以按钮中心为起点。
不要使用多个独立的波纹圈追赶主波纹(160926 经验,已弃用的初版设计)。 初版生成了 3 个独立的圆形边框 <div>,借助各自的 backdrop-filter:blur 比主波纹扩散得更快、更远——实际反馈:“效果太卡了……波浪比后面的白色块还快?不是应该在后面的白色块上做效果吗?”这两个问题叠加在了一起:
- 确实卡顿: 每帧对多个大型
<div>(直径超过屏幕覆盖半径的 2 倍)使用backdrop-filter:blur,会非常消耗 GPU——这不是错觉,而是真实的渲染开销。 - 概念错误: 波纹脱离主波纹,使用各自的时间轴(独立的
setTimeout+el.animate())运行 → 无法完美同步,正如用户观察到的“波浪比白色块还快”。
正确做法:直接在主波纹元素上叠加多层 filter:drop-shadow(...)——drop-shadow 会沿着已被 clip-path 裁切的边缘绘制(不同于 box-shadow,它沿矩形边界绘制,不会遵循裁切后的形状)。由于这是应用在同一元素上的滤镜,并且使用同一个动画,发光边缘会在每一帧自动紧贴主波纹,不需要同步,不需要辅助元素,也不需要 backdrop-filter:
.theme-reveal{position:fixed;inset:0;z-index:300;pointer-events:none;will-change:clip-path;
filter:
drop-shadow(0 0 1px rgba(255,255,255,.95))
drop-shadow(0 0 5px rgba(255,255,255,.45))
drop-shadow(0 0 14px var(--reveal-tint,rgba(10,132,255,.35)))}
/* duration/easing: xem §4 khối JS bên dưới + "bài học easing 160926 (v3)" */
三层 drop-shadow 从清晰锐利的发光边缘(水边)→ 柔和的白色光晕(折射)→ 根据 --reveal-tint 显示的宽广彩色光晕(目标模式)依次叠加——看起来像真实的波纹/液态玻璃,随着上涨的水边一起卷动,而不是彼此分离、飞散的波纹。
(function(){var K='<tên-trang>-theme',d=document.documentElement,nav=document.querySelector('nav');if(!nav)return;
function isDark(){var t=d.getAttribute('data-theme');return t?t==='dark':matchMedia('(prefers-color-scheme: dark)').matches}
var sw=document.createElement('div');sw.className='theme-switch';sw.setAttribute('role','switch');sw.setAttribute('tabindex','0');
sw.innerHTML='<span class="track"><span class="knob"></span></span>';
var row=document.createElement('div');row.className='theme-row';
var lb=document.createElement('span');lb.className='lbl';lb.textContent='Giao diện';row.appendChild(lb);row.appendChild(sw);nav.appendChild(row);
function paint(){var dk=isDark();sw.classList.toggle('on',dk);sw.setAttribute('aria-checked',dk?'true':'false');
sw.setAttribute('aria-label',dk?'Nút gạt giao diện: đang tối — gạt sang sáng':'Nút gạt giao diện: đang sáng — gạt sang tối')}
var busy=false;
function commit(next){d.setAttribute('data-theme',next);try{localStorage.setItem(K,next)}catch(e){}paint()}
function flip(e){
if(busy)return; var next=isDark()?'light':'dark';
var r=sw.getBoundingClientRect();
// điểm tỏa = con trỏ chuột lúc bấm, KẸP trong biên nút; Enter/Space (không toạ độ) -> tâm nút
var x=(e&&typeof e.clientX==='number')?Math.min(Math.max(e.clientX,r.left),r.right):r.left+r.width/2;
var y=(e&&typeof e.clientY==='number')?Math.min(Math.max(e.clientY,r.top),r.bottom):r.top+r.height/2;
var reduced=matchMedia('(prefers-reduced-motion: reduce)').matches;
// <tên-trang>: đổi 2 màu + 2 tint này khớp nền light/dark thật của trang (xem §3 Palette)
var bg=next==='dark'?'#09090b':'#f7fbff';
var tint=next==='dark'?'rgba(90,168,255,.28)':'rgba(10,132,255,.20)';
var canAnimate='animate' in document.createElement('div');
busy=true;
var el=document.createElement('div');el.className='theme-reveal';
el.style.cssText='position:fixed;inset:0;z-index:300;pointer-events:none;will-change:clip-path;background:'+bg;
el.style.setProperty('--reveal-tint',tint);
document.body.appendChild(el);
if(reduced||!canAnimate){
// fallback bắt buộc (FR-007, chuẩn motion.md của hallmark): KHÔNG bỏ hẳn overlay — collapse
// về crossfade ngắn ≤150ms thay vì hiệu ứng spatial full-screen, KHÔNG có crest-glow.
el.style.cssText+=';opacity:0;transition:opacity .075s linear';
requestAnimationFrame(function(){el.style.opacity='1'});
setTimeout(function(){commit(next);el.style.opacity='0';setTimeout(function(){el.remove();busy=false},90)},90);
return;
}
var maxR=Math.hypot(Math.max(x,innerWidth-x),Math.max(y,innerHeight-y));
// cấu trúc TỈ LỆ (bài học 160926 v4): chỉnh MỘT số TOTAL_MS, grow/fadeOut tự chia theo tỉ lệ cố định —
// không sửa 2 con số rời rạc mỗi lần user đổi ý về tốc độ.
var TOTAL_MS=20,GROW_RATIO=.8,growMs=Math.round(TOTAL_MS*GROW_RATIO),fadeMs=TOTAL_MS-growMs;
var grow=el.animate([
{clipPath:'circle(0px at '+x+'px '+y+'px)'},
{clipPath:'circle('+maxR+'px at '+x+'px '+y+'px)'}
],{duration:growMs,easing:'cubic-bezier(.55,0,1,.45)',fill:'forwards'}); // ease-in tăng tốc (bài học 160926 v3): càng gần lúc phủ hết càng nhanh
grow.onfinish=function(){
commit(next); // đổi data-theme ĐÚNG lúc phủ hết, trước fade
var fadeOut=el.animate([{opacity:1},{opacity:0}],{duration:fadeMs,easing:'ease-out',fill:'forwards'});
fadeOut.onfinish=function(){el.remove();busy=false};
};
}
sw.addEventListener('click',flip); // click event tự mang clientX/clientY
sw.addEventListener('keydown',function(e){if(e.key==='Enter'||e.key===' '){e.preventDefault();flip(e)}});
paint()})();
⚠️ 160926 缓动经验(v1,已在下方的 v2 中替换): cubic-bezier(.3,0,.1,1)(初版设计)会在前约 20% 的时长内压缩几乎全部动画,随后剩余时间几乎完全停滞——这与“开始慢、结束快”的意图完全相反。只有通过多个时间点的真实截图测量才发现这一问题(在 130/560ms,即时长的 23% 时,覆盖层已几乎铺满整个屏幕)。改为 cubic-bezier(.42,0,1,1)(标准 CSS ease-in,560ms)后,动画会贯穿整个 duration,但点击后紧接着的瞬间仍然较慢。
⚠️ Easing 经验教训 160926(v2,已在下方 v3 中替换): v1 发布后的真实反馈:“还是感觉有延迟、卡顿”。根本原因不是卡顿,而是 ease-in(即使过渡均匀)在 t=0 时的导数仍约为 0,因此点击后的前 100-150ms 内,几乎看不到叠加层移动 → 感觉就像“应用卡住了”。改用 cubic-bezier(.16,1,.3,1)(ease-out expo),并将 duration 从 560 缩短到 280ms(grow),从 180 缩短到 70ms(fadeOut)——感知总时长从 740ms 降至 350ms。
⚠️ Easing 经验教训 160926(v3): 后续反馈:“半秒内完成还是太久了” + “越接近结束越快”。这传递出两个信号:(1)350ms 仍然被认为慢——用户并没有固定的“足够快”阈值,应持续缩短,直到仍能看出一个圆形扩展开来(而不是闪一下)为止;(2)想要的曲线形状是逐渐加速,直至覆盖完成,而不是 ease-out(末尾减速,会让人觉得“最后还剩点事没做完”)。由于 duration 已经极短,ease-in 前段的“缓慢”已没有足够时间被感知为延迟,因此 v1 与 v2 的矛盾只会在 duration 较长时出现。将 easing 改为 cubic-bezier(.55,0,1,.45)(ease-in-quad,匀加速,以最高速度结束)。总结出的规律: duration 越短,easing 曲线形状越不重要——优先缩短 duration,再调整 easing。
⚠️ Easing 经验教训 160926(v4,结构): 用户在多轮反馈中不断要求缩短 duration(350→200→50→20ms),反馈是:“用比例结构吧,只输入最大时间,然后自动重新分配 duration”。与其使用两个需要每次手动修改的独立 duration 数值,不如定义一个 TOTAL_MS 和固定的 GROW_RATIO(0.8),让 grow/fadeOut 按比例自动计算——调整速度只需修改一个数值。TOTAL_MS=20(growMs=16,fadeMs=4)时,仍能稳定通过下方脚本的验收测试——但这已经是临界点:总时长低于约 20ms 后,肉眼几乎无法分辨“圆形扩展开来”的效果与即时变色(闪一下);再缩短只剩理论意义。
⚠️ Easing 经验教训 160926(v5,定稿): v4(TOTAL_MS=20)之后仍被嫌 “还是太久了” → 用户最终决定 “直接做成瞬时 [instant] 吧”。对于点击后直接触发的反馈效果,用户实际能够接受的阈值无法事先用数字确定——对于 theme-toggle 这类高频点击按钮,最稳妥的做法是在投入设计复杂的 reveal 效果(overlay/clip-path/crest-glow)之前先询问,而不是默认总要有动画效果。当用户要求“瞬时”时:彻底移除 overlay 和 el.animate,flip() 只保留 commit(next)——不要再把 TOTAL_MS 一点点往下调;如果没有其他元素会创建该 class,也一并删除 CSS .theme-reveal。可参考 llmwiki/html/overstack.html。此技能中的圆形 reveal 仍是适用于点击较少的 landing/showcase 页面的良好默认模式——不要不加区分地用于所有场景。
⚠️ 为极短 duration 编写验收脚本时的陷阱: await animation.finished(Promise)可能会在 'finish' 事件触发之前 resolve——而真实代码中的 onfinish(调用 commit() 的地方)监听的是事件,不是 Promise。通过真实浏览器测量(getAnimations()[0].playState)才能发现:Promise 已经 resolve,但 data-theme 仍未改变。测试必须通过 addEventListener('finish', ...) 等待完成(或先检查 playState==='finished'),才能与真实 onfinish 的触发时机正确同步——参见 scripts/verify-theme-motion.mjs。一般经验:不要硬编码毫秒数来猜测“动画结束了没有”——应使用真实的 API/事件,这样在各种 duration 下都更准确。
没有侧边栏的页面(单栏 landing)?将它放在页面页脚同一行,仍然使用带标签的拨动按钮——绝对不要再改回角落里悬浮的图标芯片。
自动验收
npm install --no-save @playwright/test # cài tạm, không cần lưu vào package.json
node skills/dark-mode-maker/scripts/verify-theme-motion.mjs <file.html>
断言:overlay .theme-reveal 出现在光标的准确坐标处(限制在按钮边界内)+ 背景色为目标模式;覆盖整个视口后才更改 data-theme/localStorage;淡出后 overlay 自动从 DOM 中移除;prefers-reduced-motion: reduce 分支不会出现扩散的 clip-path(平滑交叉淡入淡出,不产生空间运动)。
分支
| ID | 类型 | 守卫条件 | 行为 | 跳过/失败 | 汇合 |
|---|---|---|---|---|---|
| B01 | conditional_required | prefers-reduced-motion: reduce 或没有 el.animate | 使用 ≤150ms 的交叉淡化,不使用扩散式 clip-path,也不使用 crest-glow(JS §4 中的回退方案) | — | W06 |
| B02 | conditional_required | 页面没有侧边栏(单列落地页) | 放在页面页脚同一行,仍为带标签的切换按钮,而不是角落里的图标芯片 | — | W02 |
| B03 | user_optional | 用户要求“instant”(v5 课程) | 完全移除遮罩层和 el.animate,flip() 只保留 commit(next),若 CSS .theme-reveal 不再使用则删除 | — | W06 |
验证与停止条件
使用 scripts/verify-theme-motion.mjs 在真实浏览器中进行确定性验收(不要硬编码毫秒数——等待 finish 事件)。脚本通过后即停止;美观度(调色板色调、感知速度)需要用户目视确认。
示例
- 正向示例: 展示页面
site/index.html有<nav>侧边栏 → 添加 CSS §1 + FOUC §2(锁定site-theme)+ 调色板 §3 + JS §4 →node skills/dark-mode-maker/scripts/verify-theme-motion.mjs site/index.html通过:遮罩层从按钮内准确的点击位置扩散,覆盖完成后data-theme改变,遮罩层从 DOM 中移除。 - 边界/失败: 为了“保险”,在
.theme-row上保留backdrop-filter:blur→ 截图中露出一条独立的双层磨砂玻璃带 → 违反 RULE-02,移除backdrop-filter,设为background:transparent。