Files
tamabox/dev/theme-builder/README.md
T
tamakyi 937a8901b2 fix(theme-builder): 图层换不了图、背景关不掉、草稿清不掉
三个问题都是交互设计造成的,核心逻辑其实没坏(实测换图是生效的):

1. 「每层都只有这张图、换不了」
   - 加层时 l.asset 默认绑 S.assets[0],连加三层全是同一张;
   - 换图的下拉只在「选中那一层」的编辑器里,用户根本看不出每层能各自换。
   改为:新层不自动绑图(素材池只剩一张时才绑),每行带缩略图 + 「图片」下拉直接换。

2. 「背景图一直保留、关不掉」
   - 「跟随全局」状态下背景下拉是 disabled,用户在箱主页看到背景还在又点不动。
   改为:一律可点,一改就自动把这一页切成独立配置,并把全局当前值复制过来当起点
   (改背景不会顺带把图案和图层弄丢;图层重新发 id,避免两个作用域共用一批 id)。

3. 「上次的数据关闭后还在」
   - 之前只能靠隐藏参数 ?fresh=1 绕过 localStorage 草稿,UI 上没有出口。
   顶栏加「新建」:清空工程 + 删草稿,二次确认。

顺带:
- 「加一层」不再因 inherit 而 disabled(点了自动切独立),改为独立后若全局层数已达 10 会明确提示;
- 跟随全局时不给拖拽框 —— 那时 cfgOf 返回的是 global 对象,一拖就改到全局去了;
- 编辑器里删掉重复的图片下拉,只留尺寸。

回归:?dump=layers 全绿 —— 加层后 asset 为 [null,null]、行内下拉 2 个、
换第 2 层后 [null,blue] 且 CSS 只出现 blue;box 页 inherit 时 addDisabled/bgDisabled 均为 false;
在 box 加层后 inherit=false、本页 3 层而 **全局仍是 2 层**(没被污染);
新建后 assets 0、层 0、草稿删除、scope 回 global。
另外 ?dump=fx 13 项、?dump=validate 空、生成的 CSS 过 TestThemeCSSComplete + TestDecoSlotCount。
2026-10-20 15:56:59 +08:00

188 lines
9.8 KiB
Markdown
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.
# 主题生成器
一个可视化编辑主题的小工具:**以项目真实页面为画布**,按页面分别摆装饰,导出 zip 解压即用。
它替换了早先那版「单文件、仿制预览」的生成器 —— 那一版的问题很直接:预览是我自己画的
示意图,跟真实页面长得不一样;而且只能配「背景 + 人物 + 道具」三件套。
## 为什么需要起一个本地服务
生成器要把 `outputs/preview-*.html`(由 `go test` 渲染出来的**真实页面**)放进 iframe 当画板,
再往里注入正在编辑的主题 CSS。`file://` 下浏览器不允许父页面访问 iframe 的 DOM(即使同目录),
预览就只能退回示意图。起本地服务后父子同源,拖拽、注入、实时改令牌都能工作。
```bash
# 1) 先渲染真实页面快照(改了模板/数据后要重跑)
TEST_PREVIEW=1 go test ./templates -run TestGenPreview
# 2) 起服务(只读,不改项目任何文件)
python dev/theme-builder/serve.py # 默认 8777
# 3) 打开
http://127.0.0.1:8777/dev/theme-builder/
```
不想用脚本也行:在项目根跑 `python -m http.server 8777`,效果一样。
## 它和线上是同一条链路
| 环节 | 生成器 | 线上 |
| --- | --- | --- |
| 画板 | `outputs/preview-*.html`(真模板渲染) | 同一套模板 |
| 作用域 | iframe 里设 `data-page-scope` | `internal/context.pageScope()` 输出到 `<html>` |
| 装饰层 | **固定 10 槽** | `base.css` 的 12 层背景(pattern 1 + deco 10 + image 1) |
| 位置 | `calc(0%|50%|100% ± Npx)` | 同一套 `background-position` |
因为是同一套机制,工具里拖出来的位置换视口不会跑位,也不会出现「预览好看、实机错位」。
## 三块面板
**页面** —— 每个页面作用域可以单独配,各有一份背景与装饰:
| 作用域 | 对应路径 |
| --- | --- |
| 全局 | 所有页面默认 |
| `box` | `/_/{domain}` 箱主页 |
| `question` | `/_/{domain}/{id}` 单题页 |
| `admin` | `/user/questions` 题目列表 |
| `profile` | `/user/*` 设置页 |
| `vote` | `/vote/{token}` 投票页 |
默认全部「跟随全局」;勾掉就变成独立配置,导出时才会产出覆盖块。
没配的页面自动沿用全局,不会出现「某页装饰消失」。
以前「跟随全局」时背景下拉和「加一层」都是 disabled —— 用户在箱主页看到背景还在、
又点不动,这就是「背景图关不掉」的来源。现在:
- 「加一层」「改背景」永远可点,**一改就自动把这一页切成独立配置**,并把全局当前值复制过来当起点
(改背景不会顺带把图案和图层弄丢;图层重新发 id,避免两个作用域共用一批 id,拖拽/高亮会串);
- 这种状态下图层列表不展示全局的层、也不给拖拽框 —— 否则一拖就改到全局去了;
- 想主动切换用「跟随全局」复选框,或用列表里的「改为独立配置」按钮。
(首页 `/` 与 `/explore` 不列出来 —— 全站公开页不注入 `ThemeSlug`,不加载主题 css,配了也没效果。)
**装饰图层** —— 每个作用域最多 10 层,三种类型:
- **图片**:从图片池选,按高度定尺寸(宽度自动等比),选中后可直接在画板上拖动;
- **光斑**:`radial-gradient`,给空白一点体积,不占位、不会变成贴纸;
- **线性光**:`linear-gradient`,做窗光/压深。
锚点用 9 宫格 + 像素偏移,生成 `calc(0% + 40px) calc(100% - 36px)` 这种两值形式。
**每一行都能直接换图**:行里带缩略图 + 「图片」下拉,一眼看到这层用的是哪张。
之前换图的下拉只出现在「选中那一层」的编辑器里,加上新层又默认绑第一张素材,
连加三层就全是同一张图,看起来像「每层只能显示这张、换不了」。
现在新加的层**不会**自动绑第一张(只有素材池只剩一张时才绑),行里显示「(选图片)」等你选。
**↑ / ↓ 调层级**。顺序就是层级:`background-image` 的第一层画在最上面,
所以「谁挡住谁」是靠顺序定的(shiroko 里自行车被人挡住就是这么来的)。
**窄屏** —— 手机上卡片几乎占满屏幕,桌面那套 16:9 场景会被 `cover` 裁成一条无意义纹理,
固定 px 的人物会被卡片盖住。这一栏可以:指定一张竖版背景图、决定装饰是否退场、
改断点(默认 900px)。导出时生成对应的 `@media` 块,只写浅色规则
(深色块特异性更高,本来就压住它)。
**图片池** —— 上传任意张,浏览器本地转 WebP,画布尺寸 = 原图尺寸(不缩放、不留透明边)。
导出时只打包实际用到的图。每张图左上角有 `fx` 按钮,进处理面板(处理过的会高亮)。
### 图片处理
| 功能 | 说明 |
| --- | --- |
| 不透明度 | 0~100%。CSS 背景图没法单独设 `opacity`,只能烘焙进像素 |
| 压白 / 雾度 | `v×(1-k)+255×k`。场景压白 38% 时人物常用 12~26%,否则是全页对比度最高的一块 |
| 去色 | 往灰度混。彩色素材压不住素场景时用 |
| 裁掉透明边 | 生成图四周留白多时裁掉,否则按高度定尺寸会让主体看着比实际小 |
| 去白底 | 四边漫水 + 封闭白孔检测,默认容差 16 |
| 边缘收 1px | 去白底后把残留的半透明白边收掉,代价是轮廓缩一圈 |
**非破坏**:原文件字节存在 `orig` 里,`fx` 只存参数,每次处理都从 `orig` 重算。
所以「压白 16% → 改成 20%」不会叠加两次压缩,反复调参也不会把边缘越磨越糊。
打开面板看一眼就关,一个像素都不会动(参数没变就跳过重编码)。
**去白底的两个关键点**(都是踩过的):
- **封闭白孔**:手内侧、两腿之间那种被轮廓包住的白色,四边漫水根本进不去,
必须单独按连通域掏(面积 ≥ 24 才掏,更小的多半是眼睛高光)。可以关掉。
- **容差别贪**:背景常见 253,白袜子 237,只差 16。容差 16 保得住袜子,30 就吃穿了。
实测白底立绘去白底后透明占比 46.3%,与手写 floodfill 抠出来的 46.6% 差 0.3%。
## 工程文件与草稿
调主题是连续几十次的活,所以配置必须留得住:
- **新建**:顶栏按钮,清空当前工程并删掉浏览器草稿(有二次确认)。
之前只有 `?fresh=1` 这个隐藏参数 —— 用户看到「上次的数据」一直在,就会以为关不掉。
- **自动草稿**:每次改动后 700ms 存进 `localStorage`,下次打开自动恢复。
图片多到顶破 5MB 配额时会提示改用 json(只提示一次)。
- **保存工程 / 打开工程**:`theme-<slug>.json`,图片以 base64 内嵌,一个文件就能带走;
可以进版本库,也可以发给别人接着改。
- 不想恢复草稿就加 `?fresh=1`。
## 导出
点「导出 zip」得到:
```
static/css/themes/<slug>.css
static/images/themes/<slug>/*.webp
INSTALL-<slug>.txt
```
点导出时会先跑一遍校验(slug 合法、图片层是否选了图、有没有引用已删除的素材、
装饰是否超过 10 层、生成出来的 CSS 每处装饰是不是真的 10 个值),有问题直接列出来,
不会让你导出后再去等 `go test`。
解压到项目根,然后按 `INSTALL-<slug>.txt` 做三步:
1. `conf/app.ini` 的 `[theme] available` 加上 slug;
2. `internal/conf/static.go` 的 `StaticVersion` +1;
3. `go build`(模板与静态资源都是编译期 embed,只重启不重编不会变)。
## 它不做什么
- **不做复杂修图**:没有笔刷、蒙版、自由裁切、调色曲线 —— 这些该在专门的工具里做完再进来。
- **不做调色**:想统一「雾度」(人物压白 16%、场景 30~38%)得在生成端做,
见 `docs/theme-shiroko-assets-spec.md`。
- **不写配置**:`[theme] available` 只给要粘贴的那一行。
- **不碰深色的图片**:深色块一律 `--tb-page-image: none` + 装饰全关(白天场景压在深底上会脏)。
想在深色下也放图,需要手工加深色块。
## 自检
没有 UI 也能回归,两个钩子:
```
?demo=1 载入示例素材(canvas 画的背景 + 剪影人物)
?rows=2 加 2 层并依次绑到不同素材(截图多图层效果用)
?scope=admin 直接落在某个作用域
?fresh=1 忽略 localStorage 草稿,从空白开始
?dump=css 把最终 CSS 写进 <pre id="dump">
?dump=zip 把 zip 的 base64 写进 <pre id="dump">
?dump=validate 把 validate() 的结果写进 <pre id="dump">
?dump=install 把安装说明写进 <pre id="dump">
?dump=fx 跑处理算法自检(13 个断言,输出 JSON)
?dump=fxpipe 跑弹窗链路冒烟(打开→改参→预览→落盘→无改动不重编码)
?dump=layers 图层/作用域回归(加层默认不绑图、行内换图、自动切独立、新建清空)
?dump=fxkey 端到端去白底:载入白底图,统计透明像素比例
?loadurl=/static/images/x.webp&fx=x
直接把项目里的图当素材载入(不必先下载再上传),fx= 打开处理面板
?fxjson={"key":{"on":true}}
配合 loadurl 给初始处理参数
```
```bash
chrome --headless=old --virtual-time-budget=6000 \
--dump-dom "http://127.0.0.1:8777/dev/theme-builder/?demo=1&dump=css"
```
把抓到的 CSS 存成 `static/css/themes/<slug>.css`,可跑:
```bash
go test ./internal/theme -run "TestThemeCSSComplete|TestDecoSlotCount"
```
`TestDecoSlotCount` 是这次新增的:它会逐个检查 `base.css` 与所有主题 css 里每一处
`--tb-deco*` 声明是否都有 10 个值。少写一个不会报错,只会让图层与 size/position 错位
(表现为整页重复同一张插画,或人物跑到左上角),靠注释守不住。