200 lines
15 KiB
Markdown
200 lines
15 KiB
Markdown
# 2026-10-20
|
||
|
||
## 01:10 shiroko 主题补「空气层」
|
||
|
||
- 用户问「还有没有其他元素,贴合主题又不突兀」。结论:场景压白 38% 后整页亮度只有 235~246,
|
||
再摆物件(书包/粉笔/纸飞机)一定会像贴纸,所以走**纯渐变造光**路线。
|
||
- 做了 4 个候选截图(A 纯窗光 / B 白散景 / C 光环符号 / D 组合),用户选 D。
|
||
⚠️ **B 是反面教材**:白色散景叠在已经压白的场景上,实测差异 0.14 级,肉眼完全不可见。
|
||
要"提亮"必须先有暗部,真正起作用的是「底部压深 + 暖色光斑」的组合。
|
||
- 最终 `--tb-deco` 后 4 槽:左窗光柱(112deg)、右侧 120px 暖散景(88% 30%)、
|
||
脚下 560×170 地面光斑(28% 94%)、底部 24% 压深。亮度差 3~9 级。
|
||
- 验证手段(本轮**模型读不了图**,全靠数值):留白区差分(与候选 D 差 0.0)、
|
||
分区亮度表、横向扫描相邻像素差 max 4(判色阶断层)、PNG 体积比对。
|
||
- ⚠️ 造预览页两个坑:① 覆盖块必须包在 `<style>` 里,裸写会被当正文渲染,整页布局炸掉;
|
||
② `--tb-deco` 各层之间**漏逗号**会让整条声明失效 → 人物和所有背景层全没,且各变体看起来"完全一样"。
|
||
以后先 diff 两个变体的 CSS 文本再截图。
|
||
- `StaticVersion` → `20261020a`;提交 `fb94b8c` 已推送。文档 `docs/theme-shiroko.md` 加「空气层」一节。
|
||
|
||
## 08:30 重画光环
|
||
|
||
- 用户反馈「光环变形太奇怪,而且中间没有扣干净」。根因两条:
|
||
① AI 画的环本身歪(bbox 291,93–584,232,比头还宽、偏移到左边);
|
||
② 环内是**封闭白孔**(面积 5190px 的包围区域),四角 floodfill 进不去 → 抠完环里糊着一团白。
|
||
- 做法:非白像素从环上一点漫水拿到独立连通域(与头发不连通,头部顶端在 y≈265,环只到 232),
|
||
整块抹白;**先把 alpha 建好,再在 RGBA 上画新环**——环心天然就是透明的,不用给封闭孔补洞。
|
||
- 新环参数:中心 (515,160)、100×34、线宽 10、-7° 倾斜、4 倍超采样后 LANCZOS 缩小、
|
||
外加 45% 模糊柔光;颜色 (127,211,220),压白 16% 后实测环心 (164,220,224)。
|
||
- 坑:擦除矩形右边界要绕开**灰色狼耳**(x>630,y 190-260,颜色 (139,140,146) 不是青色),
|
||
差点一起抹掉。判据是连通域 bbox 而不是"上方全是空白"。
|
||
- 验证:alpha 字符图确认环闭合、环心 alpha=0;渲染后页面里青色像素 bbox (201,467)-(271,649),
|
||
环在 y 468-486,头顶约 505,间距正常。
|
||
- `StaticVersion` → `20261020b`,图片 `?v=20261020b`;提交 `2e9bd6b` 已推送。
|
||
|
||
|
||
## 09:40 部署反馈三修 + 自行车/步枪
|
||
|
||
- **狼耳顶端被削平**:上一版用矩形 `(281,83)-(594,242)` 抹旧光环,右下角正好切到左耳尖
|
||
(耳尖在 y≈185、x 420-480)。改成只抹光环的**非白连通域**(bbox 291,93-584,232,与头发不连通)。
|
||
- **手内侧 / 大腿内侧白没抠干净**:那是**闭合**的白色区域,四角 floodfill 进不去。
|
||
加封闭白孔检测(>=246 且不与边缘连通、面积>=40)整块掏成透明。
|
||
⚠️ 阈值必须 >=251 才能当"背景":背景恒为 (253,253,253),而**白袜子是 237**,
|
||
用 246 当背景阈值会把袜子一起吃掉(第一版就吃穿了一只袜子)。
|
||
- **人物像站在课桌上**:原场景前景全是课桌,根本没有地面。重出场景:
|
||
前景留空地、课桌退到右后方,人物脚下才是地板。
|
||
- **自行车 + 步枪**:独立素材 `props/bike.webp`(镜像成车头朝右、与人同向、压白 26%、
|
||
自带接触阴影),放在**人物身后偏左**。deco 层顺序 = 画在上面的在前,所以第一层是人、
|
||
第三层是车,车天然被她挡住。
|
||
- ⚠️ **`background-position` 的百分比是相对 `(定位区宽 - 图片宽)`**,不是相对视口宽。
|
||
人(117px)和车(397px)用同一个 `calc(50% - 901px)` 会差出 280px,车直接跑出屏幕只剩一片轮圈。
|
||
按各自宽度反解后:人 `calc(50% - 524px)`、车 `calc(50% - 612px)`。
|
||
- ⚠️ 道具**不能烘焙进背景**:背景走 `cover`,视口一变裁切量就变,和固定 px 的人物必然分家。
|
||
- 场景重出后色调用「逐通道均值/方差对齐旧场景」保持同一雾度;移动端从同图竖裁,避免两个房间。
|
||
- `StaticVersion` → `20261020d`;提交 `15aebd6` 已推送。
|
||
|
||
## 09:55 素材规格书
|
||
|
||
- 用户打算换工具自己生成素材 → 写了 `docs/theme-shiroko-assets-spec.md`:
|
||
画布/导出/显示尺寸、比例(人 1:3.41、道具 1.53:1、背景 16:9)、雾度(人 16%、车 26%、场景 30~38%+模糊 1.2~1.4px)、
|
||
构图硬约束(左侧 22% 前景必须是空地,否则又回到"站在课桌上")、换图清单与定位公式。
|
||
- 配色给的是**压白后的显示值** + 反算公式 `原色=(显示值-40.8)/0.84`,并特别标注袜子要用 `#ededed`
|
||
而不是纯白(纯白会和背景粘连,抠图必然吃穿)。
|
||
|
||
## 10:40 主题生成器(dev/theme-builder)
|
||
|
||
- 单文件 `dev/theme-builder/index.html`:上传背景/人物/道具 → 拖拽调位置 → 导出 zip,
|
||
解压到项目根即可新增主题。不联网、不起服务、图片全在浏览器本地转 WebP。
|
||
- 预览用**和线上同一套机制**渲染(pattern + deco 6 层 + page-image,size/position 对齐),
|
||
不是另画示意图;拖出来的「左沿像素」反解成 `calc(50% - Npx)`,换视口不跑位。
|
||
- 导出包:`static/css/themes/<slug>.css` + `static/images/themes/<slug>/*.webp` + `INSTALL-<slug>.txt`。
|
||
CSS 同时写浅色/深色两块,10 个必需令牌齐全——**实测通过 `TestThemeCSSComplete`**。
|
||
- zip 自己实现(STORE + CRC32,约 40 行),不引 CDN;`unzip -t` 无错。
|
||
- 踩坑:① 面板里 `c.text2` 写成 undefined(light 对象里才算出来的派生色,要用 `light.text2`);
|
||
② `contain` 模式如果不按内容裁画布,会留大片透明边 → 导出宽高比全错;
|
||
③ 预览容器 `grid-template-columns:340px 1fr` 会被 1512px 的 frame 撑破 → `minmax(0,1fr)`;
|
||
④ 无头 Chrome 下 `canvas.toBlob` 的回调在 `--dump-dom` 前不触发 → 全部改 `toDataURL` 同步化,
|
||
自检才能跑(`?selftest=1` 输出 CSS/zip 字节,`?demo=1` 载入示例素材)。
|
||
- ⚠️ 预览里深色必须走「暗色渐变 + deco none」,否则会拿白天场景图压深底(和导出结果不一致)。
|
||
|
||
## 11:05 主题生成器重做:真实页面画布 + 按页面作用域
|
||
|
||
用户原话:旧生成器「只针对当前遇到的问题,需要更通用」「示例页面和我的项目完全不一样」
|
||
「想针对箱主页面、问题页面、问题列表、设置页面分别配置」。
|
||
|
||
**机制层(新)**
|
||
- `internal/context.pageScope()` 按路径归类,`<html data-page-scope>`:
|
||
`box`(/_/{domain}) / `question`(/_/{domain}/{id}) / `admin`(/user/questions,/user/votes*)
|
||
/ `profile`(/user/*) / `vote`(/vote/{token})。首页与 `/explore` 返回了值但**不加载主题 css**,
|
||
所以生成器里不列它们(列了就是骗人)。
|
||
- ⚠️ 深色覆盖必须是**三重选择器** `html[data-theme="dark"][data-theme-style][data-page-scope]`:
|
||
浅色作用域块特异性 0,2,1 与深色块相同、且写在后面,二重选择器会被它盖掉。
|
||
- 装饰槽位 **6 → 10**(base.css 背景 8 → 12 层 = pattern1 + deco10 + image1)。
|
||
`TestDecoSlotCount` 逐文件逐声明检查,变异验证有效(写 6 个会 FAIL)。
|
||
|
||
**生成器(重写)**
|
||
- 画板 = iframe 加载 `outputs/preview-*.html`(真模板渲染)。**必须起本地服务**:
|
||
file:// 下父页面读不到 iframe 的 DOM(即使同目录),预览只能退回示意图。
|
||
`dev/theme-builder/serve.py` 把项目根当静态目录(只读)。
|
||
- 每个作用域独立「整页背景 + 最多 10 层装饰」,默认跟随全局;不产覆盖块就自动回退。
|
||
- 图层 = 图片 / 径向光斑 / 线性光;9 宫格锚点 + 像素偏移;图片层可在画板上直接拖。
|
||
- ⚠️ 位置统一 `calc(0%|50%|100% ± Npx)` 两值:百分比是相对 (容器 − 图片)。
|
||
**不要** `left/bottom` 关键字混搭 —— 「水平居中 + 垂直贴底」是三值语法,非法时整条声明作废
|
||
→ 图层静默掉到左上角(这是之前踩过的坑,这次直接规避)。
|
||
- ⚠️ 配色只让填 6 个基础色,另 4 个必需令牌派生:`surface-2` 往 **text** 混(浅色下变深、
|
||
深色下变亮,方向自动对),`text-secondary/muted` 往 bg 混,`primary-weak` 加 alpha。
|
||
漏了 `--tb-primary-weak` 时 `TestThemeCSSComplete` 只报「缺少」,页面看着没问题,很容易混过去。
|
||
- 自检钩子:`?dump=css` / `?dump=zip`(写进 `<pre id="dump">`),配合 `--dump-dom` 回归。
|
||
|
||
**验证**(都是实测,不是推断)
|
||
- 生成器导出的 CSS 放进 `static/css/themes/` → `TestThemeCSSComplete` + `TestDecoSlotCount` 双绿;
|
||
删掉深色块做变异 → FAIL,说明测试真的在查。
|
||
- 真实箱主页计算样式:image/size/position/repeat/attachment **各 12 层**,前几层尺寸与 shiroko 一致
|
||
(auto 400 / 96x13 / auto 259 / 100%×3),确认扩槽没打乱顺序。
|
||
- 临时主题 `zztest` 验证:box 页有人物、question 页 `--tb-deco: none×10` 后人物消失;
|
||
不写覆盖块时回退全局(人物仍在)。
|
||
|
||
**顺手发现**:`.gitignore` 写的是 `./outputs`(无效写法),导致 247 个 outputs 文件被跟踪,
|
||
每次跑 `TestGenPreview` 都会产生一堆 M。没擅自改(会让人以为要清理历史产物),留待用户决定。
|
||
|
||
## 12:20 outputs 不再纳入版本控制
|
||
|
||
- 根因:`.gitignore` 里写的是 `./outputs`,**gitignore 不认 `./` 前缀**,整条规则等于失效,
|
||
247 个产物(预览快照、截图、历史 review 报告)一直被跟踪,每次跑 `TestGenPreview` 都产生一堆 M。
|
||
- 改成 `/outputs/` + `git rm -r --cached outputs`:索引删除 247 个,**磁盘文件一个没动**。
|
||
`git check-ignore -v outputs/x` 确认命中。
|
||
- 另两条同样无效的规则没动:`./workbuddy`(目录根本不存在,笔误)、`./scripts`(有 1 个文件被跟踪)。
|
||
用户只要求 outputs;要清理得单独确认。
|
||
|
||
## 13:10 生成器能力盘点(含实测)
|
||
|
||
用户问「有什么改进建议」,顺手实测了一个之前没验证过的点:**窄屏(390 宽)**。
|
||
|
||
- 没有 `@media` 规则的主题,在 390×844 下:16:9 桌面背景被 `cover` 裁成一条无意义纹理,
|
||
人物被占满屏的卡片完全盖住 —— 等于白配。
|
||
- shiroko 因为手写了 `@media (max-width:900px)`(换 `scene-mobile.webp` + deco 全关),
|
||
同一视口下背景是协调的竖版、装饰退场。
|
||
- 结论:**生成器导出的主题没有窄屏段**,这是真实缺口,不是理论问题。
|
||
|
||
其它已确认的缺口(grep 过,不是猜):无 localStorage / 无 json 导入导出(关掉全丢)、
|
||
无图层上移下移(遮挡关系由顺序决定,只能删了重加)、无导出前校验、无既有主题导入。
|
||
|
||
⚠️ 测窄屏时必须让 **iframe 宽度**一起变(`verify.html?w=390`)。第一次只改外层 window,
|
||
iframe 内部仍是 1512,媒体查询按 1512 判定 → 结论是错的。
|
||
|
||
## 14:20 生成器补 P0 四项
|
||
|
||
用户问建议 → 我给了 P0/P1 清单 → 他选「P0 四条一起做」。已实现并推送。
|
||
|
||
1. **配置留得住**:`localStorage` 自动草稿(renderAll 后 700ms debounce)+ `theme-<slug>.json`
|
||
保存/打开(图片 base64 内嵌,单文件带走);zip 里也塞一份 json;`?fresh=1` 跳过恢复。
|
||
2. **图层 ↑↓**:顺序即层级(`background-image` 第一层画在最上)。
|
||
3. **窄屏段**:竖版背景图 + 隐藏装饰 + 断点(默认 900),导出 `@media` 块。
|
||
⚠️ **只写浅色规则**:深色块特异性 0,2,1 > 浅色 0,1,1,本来就压住它;多写一份反而容易出错。
|
||
4. **导出前校验**:slug / 图片层缺图 / 引用已删素材 / 超 10 层 /
|
||
生成 CSS 每处装饰是否真 10 个值(与 Go `TestDecoSlotCount` 同规则,给工具自身兜底)。
|
||
|
||
**实测**
|
||
- 390 视口首层装饰 `url(char.webp)` → `none`,1512 下不变;两种视口都 12 层对齐。
|
||
- 草稿往返:带 `?demo=1` 保存 → 不带 demo 打开,`validate` 仍为空(说明恢复成功)。
|
||
- 示例 CSS 过 `TestThemeCSSComplete` + `TestDecoSlotCount`(27 处声明)。
|
||
|
||
**踩到的小坑**
|
||
- `makeDemo()` 里 `S.assets.push` 不检查重复 → 重复点「载入示例」会撞 id(`assetById` 取第一个),
|
||
导出时静默用错图。改成开头 `S.assets = []`。
|
||
- 拖拽标签写 `top:-17px` 会贴到画板边缘被压线/裁切,改成放在框内左上角;
|
||
整块半透明填充会把人物罩成一层米色(看着像"框住空气"),改为纯虚线 + 角标签。
|
||
- 面板一多左栏就滚不到底:按使用频率重排(图层 → 背景 → 窄屏 → 图片池 → 配色)。
|
||
|
||
## 15:20 生成器:图片处理(透明度 / 压白 / 去色 / 裁边 / 去白底)
|
||
|
||
用户问「图片的透明度或者一些简单的处理能否安排上」。已实现并推送 `c3d34b5`。
|
||
|
||
**非破坏模型(关键设计)**
|
||
- `orig` 存上传时的**原始字节**(`FileReader.readAsDataURL`,不重编码),`fx` 只存参数,
|
||
每次处理都从 orig 重算 → 「压白 16% → 20%」不会叠加两次 webp 压缩。
|
||
- 打开面板看一眼就关:参数没变就**跳过重编码**,一个像素都不动(`fxSnapshot` 比对)。
|
||
这条不加,用户点开看看就会把图越磨越糊。
|
||
- 预览按 420px 宽算(滑杆跟手),完成时全分辨率重算;用序号 `fxSeq` 丢弃过期结果,
|
||
否则快速拖动时旧渲染覆盖新的。
|
||
|
||
**去白底两个坑(都实测过)**
|
||
- 手内侧 / 两腿之间是**闭合**白区,四边漫水进不去 → 按连通域单独掏,面积 ≥ 24 才掏
|
||
(更小的多是眼睛高光,掏了变透明洞)。
|
||
- **容差默认 16,不是 22**:背景 253、白袜子 237 只差 16。容差 30 实测把袜子吃穿(alpha 0)。
|
||
|
||
**验证(跑出来的)**
|
||
- `?dump=fx` 13 项断言全过:封闭孔开/关、容差 16 保袜子 / 30 吃穿、收边后轮廓 1px、
|
||
alpha 50%→128、压白 20% 时 237→241、去色后 R=G=B、裁边 200×160→80×40。
|
||
- `?dump=fxpipe` 链路:弹窗开关、预览 300×960、参数重开后仍在、改参后 dataURL 变、
|
||
**无改动时 dataURL 一字节不变**(这条是防回归的关键)。
|
||
- `?dump=fxkey` 端到端:把 shiroko 立绘合成到 253 白底再去白底,透明占比 46.3%,
|
||
与手写 floodfill 抠出来的 46.6% 差 0.3%。
|
||
- 生成的 CSS 仍过 `TestThemeCSSComplete` + `TestDecoSlotCount`。
|
||
|
||
**顺带**
|
||
- 工程文件升 v3:存 `orig` + `fx`,未处理时不重复存 dataURL(否则 json 平白翻倍);
|
||
读 v2 旧工程把 dataURL 当 orig 用。
|
||
- 新增 `?loadurl=` 把项目里的图直接当素材载入(不必先下载再上传),`?fxjson=` 给初始参数。
|
||
- 截图看出两个 UI 问题:拖拽标签 `top:-17px` 贴到画板边缘被压线、半透明填充把人物罩成米色
|
||
→ 改框内左上角标签 + 纯虚线(上一轮已修,本轮沿用)。
|