之前只能算「试一次」,关掉标签页全部配置丢失,导出的主题在手机上也是废的。 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 为空)实测通过。
142 lines
6.3 KiB
Markdown
142 lines
6.3 KiB
Markdown
# 主题生成器
|
||
|
||
一个可视化编辑主题的小工具:**以项目真实页面为画布**,按页面分别摆装饰,导出 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 错位
|
||
(表现为整页重复同一张插画,或人物跑到左上角),靠注释守不住。
|