Files
tamabox/.workbuddy/memory/topics/builder.md
T

75 lines
7.2 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.
# 主题生成器 `dev/theme-builder/`(长版明细)
## 主题生成器 `dev/theme-builder/`
- `serve.py` 起静态服务(8777),iframe 加载 `outputs/preview-*.html`。**file:// 下父页读不到 iframe DOM**,必须走 HTTP。
8777 常由用户自己起着,改完直接刷新。
- 单文件 `index.html`(约 2900 行)。图片处理**非破坏**:`orig` 存原始字节 + `fx` 存参数,每次从 orig 重算;
参数没变则跳过重编码。画质(`S.export.quality/maxSide`)同理 —— 改完 `rebakeAll()` 从 orig 重来,不叠加压缩。
- **尺寸单位** 4 档:`fit`(按视口高,人物)/ `fitw`(按视口宽,横幅)/ `px` / `vh`。
`layerSize()` 出 CSS、`unitGeom()` 出预览框,**两处必须共用同一函数**,否则「框没超、图已裁」。
⚠️ 下拉的 value 用 `l.unit`,不能用 `unitOf(l)`(后者把 fit 归一成 vh → 选了 fit 却显示 vh)。
- 溢出检测 `layerMetrics(l, a, VW, VH)`:算 left/top/overL/R/T/B,编辑器里显示「显示 131×420,完整可见」或
「⚠ 左右各 88px 越界」,并给「按当前视口修好」按钮(切 px + 算刚好放下的尺寸)。导出前 `overflowReport()` 再扫一遍。
- 视口预设**带高度**(1512×900 / 1280×800 / 1024×768 / 820×1180 / 390×844):裁切几乎全由高度决定,
只改宽度模拟不出来。`fitStage()` 按预设高渲染 iframe。
- 窄屏:全局 `hideDeco`(急停)> 每层 `mob`(inherit/scale/hide)> 尺寸单位(fit/fitw 自己就缩)。
⚠️ **`hideDeco` 会把 @media 里 10 槽全写 none** —— 桌面完全正常、手机上素材全没了,
这是「素材在窄屏消失」的头号来源(test01 就是这么中招的)。UI 已在勾选时给橙色警告、
图层行标「窄屏隐藏」、加层/导出再提醒;`?narrow=hide` 可一键复现。
⚠️ `@media` 里**每个独立配置的 scope 都要写一条** `[data-page-scope="x"]`:页面覆盖块特异性 (0,2,1),
只写 `html[data-theme-style]`(0,1,1) 的话独立配置页在手机上完全不生效。
⚠️ 竖版图只替换**本来有背景图**的页面(`cfg.bg` 非空),否则等于给纯色页塞了张图。
- ⚠️ **派生文字色必须走对比度下限,不能按固定比例混**:`paletteVars` 里
`text-secondary = mixToContrast(text, backdrops, 5.5)`、`text-muted = 4.5`(WCAG AA)。
旧的 `mix(text,bg,0.55)` 在浅底只有 2.9:1 —— 每导一次主题就把 base.css 的 AA 修复覆盖回去。
- 半透明卡片(`--tb-surface: rgba(255,255,255,0.86)`)必须 `flatten()` 压到底色再算,否则按纯白算偏低;
- 参考背景取「页面底色 + 压平后的卡片」两者更不利的那个;
- 手填正文本身不达标时**原样返回**(宁可不够淡,不能产出看不见的字)。
- 配色面板有读数(正文/次要/淡文字/主色),<4.5 变橙;默认 primary 已压到 `#3d6fac`(4.63)。
- 还要导出 **`--tb-on-primary`**(白字 `#ffffff` / 深字 `#0b1016` 里挑对比度更高的):
深色模式主色偏亮,白字只有 2.2~3.1:1。主色卡中间调时两种字都不够 → 面板橙色提示 +
`?dump=palette` 的 `warnings`,**不判失败**(是主色要调,不是工具挑错)。
- 自检钩子:`?dump=css|zip|validate|install|fx|fxpipe|fxkey|layers|fit|quality|palette`,`?demo=1&fresh=1&rows=N&scope=&loadurl=`
(`?demo=1` 已含 fit 层 + 竖版背景,`@media` 才会生成)。用法:
`chrome --headless=old --virtual-time-budget=12000 --dump-dom "http://127.0.0.1:8777/dev/theme-builder/index.html?demo=1&fresh=1&dump=fit"`
抓 `<pre id="dump">`(正则要带属性:`<pre id="dump"[^>]*>`)。
- ⚠️ **canvas `toDataURL('image/webp', 1)` 不能用**:Skia 在 q=1 切无损,输出反常(噪声图 q=1 只 52KB,
q=0.95 是 440KB)。`canvasAsset` 夹到 0.95,滑块上限也是 0.95。
- ⚠️ 平涂/线稿降 q 可能**变大**(有损引入噪点)。压体积先砍长边,再降 q。缩小走 `imageSmoothingQuality='high'`。
- ⚠️ 面板只有 ~240px:`input[type=range]` 有固有宽度,必须 `flex:1 1 0;width:0;min-width:0`,
且数值放进 label 里(第三个小 flex 项会被顶出可视区)。
- `?dump=css` 的产物要过 Go 侧校验:临时存成 `static/css/themes/<非下划线名>.css` 再 `go test ./internal/theme`,
跑完删掉。
- 导出面板「素材前缀」`S.assetBase`:空 = `/static/images/themes/<slug>/`;填了 = CSS 写绝对 URL
(R2/OSS)。**图片地址只能由 `urlOf()` 决定** —— `layerCSS` 曾复制过一份拼接,改一处会出现
「背景走 CDN、装饰还指本地」。文档见 `docs/theme-assets-cdn.md`。
- **素材托管(2026-10-21)**:面板两选一 `S.host.mode = local|s3`,字段
endpoint/region/bucket/prefix/pathStyle/publicBase 进工程,**accessKey/secretKey 只进
sessionStorage**(`SEC_SESSION_KEY`),绝不进草稿/工程 json/导出 zip(有断言)。
`publicBaseOf()` 手填优先,空则按 endpoint 推导(path 风格 `<ep>/<桶>/<前缀>`)——
对 R2 这个地址不可匿名读,测试的「匿名可读」只给 warn 不拦。
- **上传走 `serve.py` 代理**,浏览器不直连对象存储:不用配 CORS,密钥不发给第三方。
端点 `/api/s3/ping`(探测能力,`python -m http.server` 没有 → 按钮禁用)、
`/api/s3/test`、`/api/s3/upload`。上传集合与导出共用 `usedAssetIds()`。
- ⚠️ **`usedAssetIds()` 返回 Set**:`!ids.length` 恒真、`ids.map` 不存在 → 上传永远报
「还没有素材」。用 `[...usedAssetIds()]` 展开。
- ⚠️ **SigV4 手写,必须对拍**:`dev/theme-builder/check_sigv4.py` 调
`dev/sigv4ref`(aws-sdk-go-v2 的 `signer.SignHTTP`)逐字节比。6/6 一致才可信。
- 只用 ASCII 用例:裸 `SignHTTP` 默认**二次转义** URI,而 S3 client 内部把
`DisableURIPathEscaping` 设成 true(单次编码)。我们跟随 S3 client / AWS 文档 RFC3986。
- canonical headers 三件套 `host;x-amz-content-sha256;x-amz-date`,与 SDK 相同;
CanonicalHeaders 自身以 `\n` 结尾,故其后与 SignedHeaders 之间有一个空行(规范如此)。
- ⚠️ **HEAD 桶失败拿不到 `<Code>`**(HEAD 响应无 body)→ 只能按状态码归因
(403→AccessDenied / 404→NoSuchBucket / 301|307→PermanentRedirect)。想拿真错误码要靠 PUT。
- ⚠️ **Windows 上 8777 可能被上一个进程继续占着**(SO_REUSEADDR 让新进程 bind 成功但抢不到
accept)。症状:`/api/s3/ping` 返回旧版 404,而代码明明改了。用
`Get-NetTCPConnection -LocalPort 8777` 查 PID,或换端口验证。
- 自测:`python dev/theme-builder/check_sigv4.py`(签名对拍)、
`python dev/theme-builder/selftest_upload.py`(假 S3,16 项)、
`python dev/theme-builder/mock_s3.py --port 9900`(手动点面板)。
- 顶栏面板 `S.navbar={mode,alpha,blur}`:solid/glass/clear 三档,基色仍取「配色 → 顶栏」,
`navbarValue()` 统一算;glass 额外写 `.uk-navbar-container:not(.uk-navbar-transparent){backdrop-filter}`。
**clear 只给预览**,`validate()` 与 `TestNavbarNotFullyTransparent` 都拦。
自检 `?dump=navbar`。