原来的生成器只能配「背景 + 人物 + 道具」三件套,预览还是自己画的仿制页面 —— 跟真实页面不像,也没法给不同页面配不同内容。 机制层: - 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」生效、 未覆盖时回退全局。
3.4 KiB
主题的页面作用域(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。
选择器与特异性
/* 全局 */
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"] 相同,
而覆盖块写在文件后面,会盖掉深色块。所以深色必须再写一条三重选择器:
html[data-theme="dark"][data-theme-style="SLUG"][data-page-scope="question"] { … }
不写的话,深色模式下会拿到浅色的覆盖块 —— 典型表现是「深色页面里突然出现一块白天场景」。
覆盖块写什么
通常只覆盖这三组:
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():
--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。