Files
tamabox/dev/theme-builder/README.md
T
tamakyi 8ffa7b0ba7 feat(theme-builder): 补 P0 四项——配置留得住、图层可排序、窄屏段、导出前校验
之前只能算「试一次」,关掉标签页全部配置丢失,导出的主题在手机上也是废的。

1) 配置持久化
- 每次改动 700ms 后写 localStorage,打开自动恢复;?fresh=1 从空白开始
- 「保存工程 / 打开工程」落成 theme-<slug>.json(图片 base64 内嵌,单文件可带走)
- zip 里也带一份 json,下次改不用从零搭
- 配额超了只提示一次,并指向 json

2) 图层上移/下移
顺序就是层级(background-image 第一层在最上),配反了以前只能删了重加。
列表标注「最上层 / 第 N 层」。

3) 窄屏段
新增窄屏配置:竖版背景图、隐藏装饰层、断点(默认 900px),导出生成 @media 块。
只写浅色规则——深色块特异性 0,2,1 > 0,1,1,本来就压住它。
实测 390 视口下首层装饰从 url(char.webp) 变成 none,1512 下不变。

4) 导出前校验
slug、图片层是否选了图、是否引用已删素材、层数是否超 10、
生成 CSS 的每处装饰是否真的 10 个值(和 Go 的 TestDecoSlotCount 同规则)。
有问题直接列出来,不用导出后再等 go test。

顺带:面板按使用频率重排(图层→背景→窄屏→图片池→配色);
拖拽框改成虚线+左上角标签,不再用半透明填充罩住人物;示例重复载入不再叠加素材。

验证:示例 CSS 通过 TestThemeCSSComplete + TestDecoSlotCount(27 处声明);
草稿往返(带 demo 保存 → 不带 demo 打开仍 validate 为空)实测通过。
2026-10-20 13:32:26 +08:00

142 lines
6.3 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}` 投票页 |
默认全部「跟随全局」;勾掉就变成独立配置,导出时才会产出覆盖块。
没配的页面自动沿用全局,不会出现「某页装饰消失」。
(首页 `/` 与 `/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,画布尺寸 = 原图尺寸(不缩放、不留透明边)。
导出时只打包实际用到的图。
## 工程文件与草稿
调主题是连续几十次的活,所以配置必须留得住:
- **自动草稿**:每次改动后 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 画的背景 + 剪影人物)
?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">
```
```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 错位
(表现为整页重复同一张插画,或人物跑到左上角),靠注释守不住。