线上(box.shiroko.one/_/tamakyi)两个现象来自同一处:`--tb-navbar` 是实色,
顶栏那条带(y=20..80)把从右上角长出来的花枝齐刷刷切掉一段。
第一版顺手改成 transparent —— 花枝完整了,但顶栏"消失":导航失去依托,
文字直接压在花纹上。用户当场否掉,要求半透明。
- 顶栏:`rgba(253,246,248,0.72)` / 深色 `rgba(34,26,30,0.72)`,配
`.uk-navbar-container{backdrop-filter:blur(12px)}`(令牌只能是颜色,模糊写在规则里)。
0.72 有依据:导航文字 #6d555d 纯底 6.34:1,最坏情况(深花瓣在顶栏后面)压平后 5.08:1。
- 花枝 top -16px → 26px:顶栏半透明后,700~760px 宽度下枝头正好压在「登录」上(截图确认)。
- 手机底栏 #tb-mobile-utils 在 base.css 写死 rgba(255,255,255,.92),粉底上是一条白杠,
主题里改成同色系 0.92。
- 新增 TestNavbarNotFullyTransparent:拦 `transparent` / `rgba(...,0)` / `#rrggbb00`,
并说明"这不是审美偏好,是踩过之后立的规矩"。变异验证过(改回 transparent 立即 FAIL)。
约定同时写进 static/css/themes/_scaffold.css 的令牌说明。
- StaticVersion 20261022b → 20261022c:css 改了不提版本号,浏览器拿着旧样式,
现象就是"改了但线上没变"。
205 lines
10 KiB
Markdown
205 lines
10 KiB
Markdown
# new-sakura(新·樱花):纯 CSS 樱花主题
|
||
|
||
和已有的 `sakura` 的区别:`sakura` 是「粉底 + 网点」的静态配色;`new-sakura` 把樱花本身
|
||
做成了**可摆放的装饰层** —— 整朵花、带缺口的花瓣、带枝的花朵树,全部是内联 SVG 的
|
||
`data:image/svg+xml` URI,写在主题 CSS 里。
|
||
|
||
因此这个主题:
|
||
|
||
- **不需要任何图片素材**,也不依赖对象存储(`static/images/themes/new-sakura/` 是空的);
|
||
- 任意视口缩放都不糊(矢量),DPI 无关;
|
||
- 换花色只改 `dev/theme-tools/gen_new_sakura.py` 里的一个色值,重跑即得;
|
||
- 没有额外 HTTP 请求 —— SVG 就在 CSS 里。
|
||
|
||
代价是 CSS 有 54KB(服务端有 gzip,实际传输约 4KB)。主题 CSS 走 `StaticVersion` 缓存,一次性开销。
|
||
|
||
## 结构
|
||
|
||
```
|
||
static/css/themes/new-sakura.css ← 生成产物,别手改
|
||
dev/theme-tools/gen_new_sakura.py ← 改这里,重跑
|
||
```
|
||
|
||
```bash
|
||
python dev/theme-tools/gen_new_sakura.py
|
||
```
|
||
|
||
生成器脚本里有三样东西:花形(`blossom` / `petal` / `branch` / `pattern`)、
|
||
每个页面作用域的位置表(`SCOPES`)、以及配色(`LIGHT` / `DARK`)。
|
||
|
||
## 每个页面的樱花位置
|
||
|
||
`--tb-deco` / `--tb-deco-size` / `--tb-deco-position` 三组各 10 槽,必须等长
|
||
(`TestDecoSlotCount` 会逐文件逐声明检查)。
|
||
|
||
| 作用域 | 选择器 | 构图 |
|
||
| --- | --- | --- |
|
||
| global | `html[data-theme-style="new-sakura"]` | 右下花枝 + 左上大花 + 两片落瓣 |
|
||
| box | `…[data-page-scope="box"]` | 右上花枝垂下(细端在外、粗端朝内)+ 左下花与落瓣 |
|
||
| question | `…[data-page-scope="question"]` | 左下花枝斜穿 + 右上大花 + 落瓣 |
|
||
| admin | `…[data-page-scope="admin"]` | 左上花枝 + 左侧底部小花(卡片占满宽度,右侧会被盖住) |
|
||
| profile | `…[data-page-scope="profile"]` | 右下花枝 + 左侧竖排小花与落瓣 |
|
||
| vote | `…[data-page-scope="vote"]` | 左上花枝 + 右侧花与落瓣 |
|
||
|
||
位置一律「关键字 + 偏移」两值写法(`right -24px top -16px`)。三值语法里混用
|
||
`left` / `bottom` 关键字是非法的,整条声明会作废、花瓣静默回到左上角。
|
||
|
||
### 窄屏(≤640px)
|
||
|
||
装饰层全部置 `none`:卡片占满宽度,左右只剩 ~16px 边距,枝条放哪儿都会糊在字上。
|
||
樱花改由底纹承担 —— `@media` 里换成**加强版 pattern**(透明度 ×1.9)并把格子收到 180px;
|
||
桌面那套 0.18~0.34 的透明度在 390px 上实测接近一片纯粉。
|
||
|
||
试过在「顶栏与卡片之间」那条 35px 的带子里放两朵小樱花:box 页卡片从 y=122 起还行,
|
||
question / admin 页卡片从 y=110 起就切掉半个,结果是黏在卡片边上的粉色碎块。手机上没有
|
||
可靠的留白,就不放装饰层 —— 观感交给配色与底纹。
|
||
|
||
⚠️ **窄屏覆盖必须重新声明每个作用域选择器**:`@media` 不提升特异性。
|
||
作用域块是 `html[data-theme-style][data-page-scope="box"]`(0,2,1),而媒体查询里常见的
|
||
`html[data-theme-style]`(0,1,1)根本压不住它 —— 表现是「手机上装饰层没关掉,桌面版照样铺开」。
|
||
深色作用域块更是 0,3,1,还得再补一条 `[data-theme="dark"][data-theme-style][data-page-scope]`。
|
||
`TestNarrowMediaOverridesScopedDeco` 会在这里把关:外层每一处非 none 的 `--tb-deco`,
|
||
媒体查询里都要求有同属性名、属性数不少于它的选择器再声明一次。
|
||
|
||
### 深色
|
||
|
||
- 花瓣换成更亮一档的粉(`#f0a0b8`),枝条提亮到能看见;
|
||
- 底纹 `--tb-page-pattern: none`:浅粉花瓣压在深底上会变成一层噪点;
|
||
- 整页背景改深色渐变(不用纯色,避免大面积死黑)。
|
||
|
||
深色块必须写成 `html[data-theme="dark"][data-theme-style="new-sakura"]`(三重选择器),
|
||
只写浅色块会被同特异性的作用域块盖掉。
|
||
|
||
## 顶栏与手机底栏
|
||
|
||
顶栏走**磨砂玻璃**,不是实色也不是全透明:
|
||
|
||
```css
|
||
--tb-navbar: rgba(253, 246, 248, 0.72); /* 亮色;深色 rgba(34, 26, 30, 0.72) */
|
||
html[data-theme-style="new-sakura"] .uk-navbar-container:not(.uk-navbar-transparent) {
|
||
-webkit-backdrop-filter: blur(12px);
|
||
backdrop-filter: blur(12px);
|
||
}
|
||
```
|
||
|
||
三种写法的实测结论:
|
||
|
||
| 写法 | 现象 |
|
||
| --- | --- |
|
||
| 实色 `#fdf6f8` | 花枝经过顶栏时被齐刷刷切掉一段,像被裁过 |
|
||
| `transparent` | 花枝完整了,但顶栏"消失":导航没有依托,文字直接压在花纹上 |
|
||
| `rgba(...,0.72)` + `blur(12px)` | 花枝透过来但被模糊成柔光,顶栏仍是一块可辨认的面 |
|
||
|
||
0.72 是怎么定的:导航文字 `#6d555d` 压在顶栏上,纯底色 6.34:1;最坏情况(深花瓣正好在顶栏后面)
|
||
压平后 5.08:1,仍过 AA。再往下调透明度就会掉到 4.5 以下。
|
||
|
||
`TestNavbarNotFullyTransparent` 会拦住 `--tb-navbar: transparent`(以及 `rgba(...,0)`、
|
||
`#rrggbb00`)—— 这条不是审美偏好,是把上面第二种写法踩过之后立的规矩。
|
||
|
||
手机底栏(`#tb-mobile-utils`)在 `base.css` 里写死 `rgba(255,255,255,.92)`,压在粉底上是
|
||
一条白杠;主题里按同一思路改成 `rgba(253,246,248,.92)` / `rgba(34,26,30,.92)`。
|
||
|
||
另外 box 作用域的花枝从 `top -16px` 下移到 `top 26px`:顶栏半透明后,700~760px 宽度下
|
||
枝头正好压在「登录」上(实测截图确认)。顶栏高 60 + 外边距 20,26px 起笔即落在顶栏下沿之上,
|
||
宽屏仍是"从右上角长出来"的观感。
|
||
|
||
## 配色与对比度
|
||
|
||
| 令牌 | 亮色 | 深色 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `--tb-bg` | `#fdf6f8` | `#221a1e` | 纯色,不用渐变(整页色感一致) |
|
||
| `--tb-surface` | `#ffffff` | `#2c2227` | 卡片 |
|
||
| `--tb-text` | `#3a2c31` (12.4:1) | `#f3e6ea` (14.0:1) | |
|
||
| `--tb-text-secondary` | `#6d555d` (6.3:1) | `#c9aeb8` (8.3:1) | |
|
||
| `--tb-text-muted` | `#77626a` (5.3:1) | `#ab9099` (5.8:1) | 踩 AA 下限 |
|
||
| `--tb-primary` | `#b8436a` (4.9:1) | `#ff93b4` (8.2:1) | 原候选 `#c14a72` 只有 4.39:1,压深 |
|
||
| `--tb-on-primary` | `#ffffff` (5.2:1) | `#1b1014` (8.9:1) | 主色按钮上的字 |
|
||
|
||
## 素材包(可选,已传到对象存储)
|
||
|
||
主题线上**不需要**任何素材:樱花是内联矢量。但同一个花形也导出成了位图,
|
||
放在对象存储上备用(`themes/new-sakura/`,共 7 个对象):
|
||
|
||
| 键 | 内容 |
|
||
| --- | --- |
|
||
| `new-sakura.css` | 主题本体(备份,便于分发) |
|
||
| `branch-right.webp` / `branch-left.webp` | 花枝 840×640 透明底(互为镜像) |
|
||
| `blossom.webp` | 单朵樱花 400×400 透明底 |
|
||
| `petal.webp` | 带缺口的花瓣 240×336 透明底 |
|
||
| `pattern.webp` | 平铺底纹 600×600 透明底 |
|
||
| `INSTALL.txt` | 包内说明 |
|
||
|
||
重新导出 / 上传(`--upload` 会一起传 CSS、位图和生成的 `INSTALL.txt`):
|
||
|
||
```bash
|
||
python dev/theme-tools/export_assets.py # 只导出到 outputs/sakura-assets
|
||
python dev/theme-tools/export_assets.py --upload # 导出并上传,读 data/r2.txt
|
||
```
|
||
|
||
`data/r2.txt` 是部署凭据(`image_endpoint` / `image_access_id` / `image_access_secret` /
|
||
`image_bucket` / `image_bucket_cdn_host`),**不进版本库**(`.gitignore` 里的 `/data/`)。
|
||
`image_bucket_cdn_host` 与 `conf/app.ini` 同名同义:只写主机名,不带 `https://` 和路径;
|
||
没有它时 `INSTALL.txt` 里的公开地址会写成占位符。
|
||
|
||
缓存策略:位图 `max-age=86400`、CSS 镜像 `300`、`INSTALL.txt` `60`。
|
||
这些键**都不带内容版本**,所以刻意不用 `immutable` —— 否则重新导出后,浏览器与 CDN
|
||
一年内拿到的都还是旧图。想要一年长缓存,得把内容哈希写进文件名(CSS 里的地址跟着换)。
|
||
|
||
⚠️ 若某个键曾经以 `immutable` 传过(本主题第一版就是这样),CDN 会一直给旧副本,
|
||
改完必须去控制台按 URL 清一次缓存,光重传没用。
|
||
|
||
### 位图版(引用对象存储)
|
||
|
||
桶绑了自定义域 `r2.337845818.best`(实测匿名 GET 200、`image/webp`、大小一致),
|
||
所以可以把 CSS 从内联切到位图:
|
||
|
||
```bash
|
||
python dev/theme-tools/gen_new_sakura.py \
|
||
--asset-base https://r2.337845818.best/themes/new-sakura \
|
||
--out static/css/themes/new-sakura.css
|
||
```
|
||
|
||
| 版本 | CSS | gzip 后 | 额外请求 | 清晰度 |
|
||
| --- | --- | --- | --- | --- |
|
||
| 内联 SVG(默认) | 53.8KB | **4.0KB** | 0 | 矢量,任意 DPR |
|
||
| 位图 | 11.8KB | 2.8KB | 5 个 webp,共 48.9KB | 2 倍图 |
|
||
|
||
服务端挂了 gzip(`internal/route/gzip.go`),所以内联版**总传输 4.0KB**,比位图版的
|
||
2.8 + 48.9 = 51.7KB 小一个数量级 —— 换位图省不了流量,只是把开销从 CSS 挪到图片。
|
||
位图版唯一正当的理由是**素材要单独复用**(分享卡、海报、其它系统)。
|
||
|
||
⚠️ 直接连桶的 S3 endpoint(`*.r2.cloudflarestorage.com`)会 403,**只有自定义域/r2.dev 可匿名读**。
|
||
切之前先 `curl -I` 验一下前缀;切完 `go build` 部署,否则 embed 里还是旧的。
|
||
|
||
## 为什么不用主题生成器出图
|
||
|
||
生成器的装饰层只支持「图片素材」(导出时打进 zip 或传对象存储),没有内联 SVG 图层。
|
||
要把樱花做成可摆放的图层,只能在设计上二选一:
|
||
|
||
1. 生成图片素材(花瓣 PNG/WebP)→ 可以用生成器、可以放 S3,但会有位图缩放、额外请求;
|
||
2. 内联 SVG 写进 CSS → 零素材、任意缩放不糊,但生成器管不到。
|
||
|
||
`new-sakura` 选 2,因为「用 CSS 做樱花」本身就是这个主题的诉求。
|
||
生成器仍然可以用来**预览**它:`?demo=1&fresh=1` 起画板,把 `new-sakura.css` 注入即可。
|
||
|
||
## 怎么验证
|
||
|
||
```bash
|
||
go test ./internal/theme # 10 槽等长、深浅块成对、必需令牌齐全、窄屏覆盖特异性
|
||
```
|
||
|
||
视觉自检用项目真实页面快照:
|
||
|
||
```bash
|
||
TEST_PREVIEW=1 go test ./templates -run TestGenPreview
|
||
python dev/theme-builder/serve.py
|
||
# 打开 outputs/preview-box-light.html,DevTools 里给 <html> 加
|
||
# data-theme-style="new-sakura" data-page-scope="box"
|
||
```
|
||
|
||
## 上线
|
||
|
||
1. `conf/app.ini` 的 `[theme] available` 加上 `new-sakura`(不改就「设置页看不到」,现象和部署没生效一样);
|
||
2. 重新 `go build` —— 主题 CSS 是编译期 embed,只重启不重编不会变;
|
||
3. 箱主设置页 → 账号 → 外观,选择「新·樱花」。
|