Files
tamabox/docs/theme-page-scope.md
T
tamakyi ddee221de2 feat(theme): 主题支持按页面分别配装饰,生成器改用真实页面画布
原来的生成器只能配「背景 + 人物 + 道具」三件套,预览还是自己画的仿制页面 ——
跟真实页面不像,也没法给不同页面配不同内容。

机制层:
- internal/context.pageScope() 按路径归类,输出 <html data-page-scope>:
  box / question / admin / profile / vote(首页与 /explore 不加载主题,不列)
- /user/questions 单独归为 admin:题目列表信息密,和设置表单的装饰诉求不同
- base.css 装饰槽位 6 -> 10,背景 8 层 -> 12 层;主题按作用域覆盖时同样写满 10 个
- 新增 TestDecoSlotCount:逐文件逐声明检查 10 个值。少写一个不报错,
  只会让图层与 size/position 错位(整页重复同一张图 / 人物跑到左上角),注释守不住

生成器:
- 画板改成 iframe 加载 outputs/preview-*.html(真实模板渲染),配 serve.py 起本地服务
  解决 file:// 下父页面读不到 iframe DOM 的问题
- 八个页面作用域(去掉不生效的首页/探索),每个可独立配背景与最多 10 层装饰
- 图层类型:图片 / 径向光斑 / 线性光;9 宫格锚点 + 像素偏移,可在画板上直接拖
- 位置统一写 calc(0%|50%|100% ± Npx):百分比是相对 (容器-图片),
  不用 left/bottom 关键字混搭,避免三值语法非法导致整条声明作废
- 配色只填 6 个基础色,其余 4 个必需令牌按方向派生(surface-2 往 text 靠,
  浅色变深、深色变亮,方向自动正确)

配套:
- zz_preview_test.go 增加单题页 / 题目列表 / 投票页预览,作为生成器画板
- docs/theme-page-scope.md 记录作用域取值、选择器特异性与 10 槽位约束
- StaticVersion 20261020e(base.css 变了)

验证:生成器导出的 CSS 通过 TestThemeCSSComplete + TestDecoSlotCount;
真实箱主页计算样式为 12 层对齐;临时主题验证「question 覆盖为 none」生效、
未覆盖时回退全局。
2026-10-20 10:53:44 +08:00

87 lines
3.4 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.
# 主题的页面作用域(data-page-scope)
一套主题过去只能有一种装饰:人物永远站在左下角,不管当前是箱主页、单题页还是题目列表。
单题页正文长、题目列表信息密,同一套装饰在三种页面上要同时成立很难。
现在服务端会按请求路径给 `<html>` 打一个 `data-page-scope`,主题可以对每个页面分别配
整页背景与装饰层。
## 取值
| 值 | 路径 | 说明 |
| --- | --- | --- |
| (无属性) | — | 全局默认 |
| `box` | `/_/{domain}`、`/_/{domain}/tag/*` | 箱主页(问题列表) |
| `question` | `/_/{domain}/{qid}` | 单个问题 |
| `admin` | `/user/questions`、`/user/votes/*` | 题目 / 私聊管理列表 |
| `profile` | `/user`、`/user/profile` 等 | 设置页 |
| `vote` | `/vote/{token}` | 投票页(跟随发布者的主题) |
| `home` / `explore` | `/`、`/explore` | Go 侧会返回,但**这两页不加载主题 css** |
来源:`internal/context.pageScope()`,测试在 `internal/context/context_test.go` 的 `TestPageScope`。
## 选择器与特异性
```css
/* 全局 */
html[data-theme-style="SLUG"] { … }
/* 某一页覆盖 */
html[data-theme-style="SLUG"][data-page-scope="question"] { … }
```
特异性 0,2,1 与深色块 `html[data-theme="dark"][data-theme-style="SLUG"]` 相同,
而覆盖块写在文件后面,**会盖掉深色块**。所以深色必须再写一条三重选择器:
```css
html[data-theme="dark"][data-theme-style="SLUG"][data-page-scope="question"] { … }
```
不写的话,深色模式下会拿到浅色的覆盖块 —— 典型表现是「深色页面里突然出现一块白天场景」。
## 覆盖块写什么
通常只覆盖这三组:
```css
html[data-theme-style="SLUG"][data-page-scope="question"] {
--tb-page-image: none; /* 不放整页场景 */
--tb-page-pattern: none;
--tb-deco: …; /* 10 个值 */
--tb-deco-size: …; /* 10 个值 */
--tb-deco-position: …; /* 10 个值 */
}
```
配色(`--tb-bg` / `--tb-surface` / `--tb-text` / `--tb-primary`)一般不要按页面改:
同一个主题在不同页面换配色,用户会觉得走错站了。
## 10 个槽位
`--tb-deco` / `--tb-deco-size` / `--tb-deco-position` 三组必须**等长且写满 10 个**。
少一个不会报错,CSS 会按列表长度循环取值,于是图层与 size/position 整体错位:
- 整页重复同一张插画(第一次踩坑就是这个)
- 人物跑到页面左上角(`background-position` 整条声明非法时回落初始值)
`internal/theme/slots_test.go` 的 `TestDecoSlotCount` 会逐个文件、逐处声明检查,
包括 base.css 与所有主题 css(含作用域覆盖块)。写主题时不用记,跑测试就行。
`background-position` 一律写成两值 `calc()`:
```css
--tb-deco-position: calc(0% + 40px) calc(100% - 36px),
calc(50% + 0px) calc(50% + 0px), …;
```
- 百分比是相对 **(容器 − 图片)** 的剩余空间,不是相对容器
- `0%` 左/上、`50%` 居中、`100%` 右/下
- **不要**混用 `left` / `bottom` 关键字:「水平居中 + 垂直贴底」会变成三值语法,
一旦不合规范整条声明作废,图层静默掉到左上角
## 可视化编辑
手写这些值不现实,用 `dev/theme-builder/`(见同目录 README):以真实页面为画布,
按页面切换、拖拽定位、导出 zip。它生成的 CSS 直接过 `TestThemeCSSComplete` 与
`TestDecoSlotCount`。