From 6b4ee89d19ab05b9e850307a33bfab539f5f6fd0 Mon Sep 17 00:00:00 2001 From: laoyang Date: Wed, 28 Oct 2026 13:57:29 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E8=AE=BE=E7=BD=AE=E5=B7=A6?= =?UTF-8?q?=E7=A7=BB=E3=80=81=E4=B8=BB=E6=93=8D=E4=BD=9C=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E7=AD=89=E5=A4=A7=E5=BC=82=E8=89=B2=E3=80=81=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=9C=80=E8=BF=91=E7=94=9F=E6=88=90=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三处按需求调整: 1. 设置区移到左边 左 = 设置,右 = 输出。不调设置的人视线直接落到右侧密码与「复制密码」。 DOM 顺序也改成设置在前,窄屏单列时天然是设置在上、输出在下。 栅格比例同步反过来(设置 .92fr / 输出 1.08fr)。 2. 「复制密码」与「换一个」等大、不同色 .out-actions 从 flex 改成 grid repeat(2, 1fr),两个按钮严格等宽; 主按钮保留渐变,次按钮改为实心填充(新增 --surface-3,比 --surface-2 更实, 避免深色下与卡片同色而看着像没填充)。 3. 移除「最近生成」模块 删面板、清空按钮,以及 readHistory / pushHistory / renderHistory / ago 与 sp.history.v1 的全部读写。候选面板改为整宽单栏。 顺带清掉了共用工具 listItem 上的历史调用点。 验证(六套,全绿): 功能 jsdom 78 / 0 视觉几何对比度 114 / 0 真实剪贴板端到端 19 / 0 file:// 直开 15 / 0 功能反向注入 22 / 22 命中 视觉反向注入 6 / 6 命中 反向注入拆成两套:_sp_falsify.js 只喂 jsdom(不做布局), 把「按钮等大」「两栏左右」这类几何 bug 注进去影响不到被测判据, 会表现成"注入后仍全绿"、被误判成判据没牙齿(靶子选错)。 几何/视觉类归 _sp_visual_falsify.js。 删历史后专补两条"确实没了"的判据:DOM 节点不存在 + localStorage 不再写入 sp.history.v1 —— 后者能抓到"面板删了但 JS 还在偷写"。 --- README.md | 17 ++-- docs/dark.png | Bin 449348 -> 391966 bytes docs/light.png | Bin 415439 -> 420212 bytes docs/mobile.png | Bin 353246 -> 326193 bytes index.html | 215 ++++++++++++++++++------------------------------ 5 files changed, 90 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index d847ab6..9242d5a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ ## 特点 - **纯前端,零依赖** — 一个 `index.html`,没有构建步骤、没有 CDN、没有任何外部请求。断网可用。 +- **左设置、右操作** — 设置栏在左,不调任何设置时视线直接落到右侧的密码与「复制密码」。窄屏自动变上下排列,设置在上。 - **密码学安全随机** — 走 `crypto.getRandomValues` + 拒绝采样消除取模偏差,不用 `Math.random()`。 - **生成即复制** — 默认开启自动复制;剪贴板 API 不可用(如 `file://` 下的旧浏览器)时自动降级到 `execCommand`。 - **深浅色** — 跟随系统偏好,可手动切换并记住选择。首帧前定主题,不会白屏闪一下。 @@ -16,7 +17,6 @@ - **自定义排除** — 想避开 `@`、空格或某个符号,直接填进去。 - **强度与熵** — 按 `长度 × log₂(字符池)` 算真实熵,分五档给出评价,同时显示字符池规模。 - **批量候选** — 一次出 5 个,点任意一条即复制。 -- **最近生成** — 只存在本机 `localStorage`,最多 6 条,可一键清空。 ## 使用 @@ -44,7 +44,7 @@ python -m http.server 8000 - 所有运算在浏览器里完成,**不联网、不上传、不写日志**。 - 用 `crypto.getRandomValues` 取随机数,并按「拒绝采样」丢弃落在偏置区间的值,避免取模带来的分布倾斜。 - 勾「每类至少一位」时,先从每类各取一位,剩余位从全池随机,最后整体洗牌 —— 保证覆盖的同时不引入位置可预测性。 -- 历史记录存在 `localStorage` 里,任何能访问这台机器的人都能看到。**在共用电脑上建议用完点「清空」**,或干脆关掉自动复制、不复制到历史。 +- **不保留任何生成记录**。密码只存在于剪贴板与页面显示,刷新即失去痕迹;`localStorage` 里只存偏好设置与主题,不存密码。 ## 浏览器支持 @@ -63,11 +63,18 @@ index.html 全部内容:结构、样式、脚本 改完 `index.html` 后建议跑一遍(脚本在仓库外的 `_tooling/`,配合 `jsdom` 与 `playwright`): ```bash -node _sp_test.js # 功能判据(jsdom,80 条,含反向注入 24 条) -node _sp_visual.js # 真实浏览器几何 / 对比度 / 多视口(约 101 条) -node _sp_clip.js # 真实剪贴板端到端(18 条) +node _sp_test.js # 功能判据(jsdom,78 条) +node _sp_visual.js # 真实浏览器几何 / 对比度 / 多视口(114 条) +node _sp_clip.js # 真实剪贴板端到端(19 条) +node _sp_file_check.js # file:// 双击直开(15 条) +node _sp_falsify.js # 反向注入,证明功能判据有牙齿(22 条) +node _sp_visual_falsify.js # 反向注入,证明几何判据有牙齿(6 条) ``` +反向注入必须单独跑视觉那套:`_sp_falsify.js` 只喂 `_sp_test.js`(jsdom 不做布局), +把「按钮等大」「两栏左右」这类几何 bug 注进去根本影响不到被测判据, +会表现成"注入后仍全绿",看着像判据没牙齿,其实是靶子选错。 + ## License MIT \ No newline at end of file diff --git a/docs/dark.png b/docs/dark.png index 467f66f61f562495e1289b3ac463a109611b445d..d547e0f23fba6d0b5fd81827f11b3233a573da59 100644 GIT binary patch literal 391966 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