Skip to content

SkinNexus 主题创作规范(theme-spec)—— 含 AI 生成提示词模板 ​

面向任何人(包括让 AI 代劳)创作 SkinNexus 主题包。零构建原则:主题 = theme.json + 纯 CSS(+ 可选 JS), 不需要 Node/打包/编译,admin「外观」上传主题包 zip(热生效)或放入 data/themes/<id>/ 并重启服务即生效。 加载器实现:server/internal/theme/theme.go(校验规则以代码为准,本规范与代码逐项对齐)。

1. 主题包布局 ​

data/themes/<id>/
  theme.json      # 必需:清单(字段见 §2)
  style.css       # 常规入口样式(inject.css 引用)
  any-asset.png   # 可选资源:平铺放在包根目录(图片/字体等)
  preview.png     # 可选:预览图(预留,当前面板不展示)
  • <id> 只能是 小写字母/数字/连字符/下划线,首字符为字母或数字(^[a-z0-9][a-z0-9_-]*$),且必须与 theme.json 的 id 字段一致。
  • 资源文件平铺放包根目录、文件名全小写(同上正则,允许点号扩展名)——assets/ 之类的子目录不可用(资源路由只解析单层文件名,多段路径会 404);CSS 里引用写 url("bg.png") 而非 url("assets/bg.png")。
  • 目录穿越(..、绝对路径)会被加载器直接拒绝(白名单正则 + 路径前缀双重校验,无法读包外文件)。

2. theme.json 字段 ​

json
{
  "id": "dark-elegance",
  "name": "暗色优雅",
  "version": "1.0.0",
  "author": "your-name",
  "description": "一句话描述主题风格",
  "inject": { "css": ["style.css"], "js": [] },
  "palette": { "primary": "#6366f1" },
  "requires": { "skin_nexus": ">=0.1" }
}
字段必需说明
id✅与目录名一致(不一致整个包被跳过并记日志)
name✅展示名
version✅x.y[.z] 版本号(可带 v 前缀,不接受 -beta 之类预发布后缀——解析不了整包被跳过);同时用作 CSS/JS 链接的 ?v= 缓存破除号(改样式请升版本号)
inject.css / inject.js✅按序注入的包内文件列表(css 与 js 至少各有一个列表非空)
author / description展示用
requires.skin_nexus版本约束,加载器真实校验:仅支持 >=x.y(可带 patch);不满足则跳过该包并记日志;约束写错(如 ~1)按不满足处理
palette声明式色板(文档/预览用)
extends / slots预留字段(解析但当前不生效,见 §5)

name/version/inject 是硬性必需——缺任何一个包会被跳过,日志(docker compose logs skin-nexus) 里有 theme: skip ... 原因;id≠目录名、JSON 坏、约束不满足同样只在日志可见。

3. CSS 变量契约(设计令牌)——优先改变量,别硬编码 ​

SkinNexus 的应用样式定义了如下 CSS 自定义属性;主题覆盖它们即可换整套配色:

css
:root {
  --bg: #101825;          /* 页面背景 */
  --panel: #16202e;       /* 面板/卡片背景 */
  --border: rgba(169, 138, 95, .24); /* 边框 */
  --text: #e9edf1;        /* 主文字 */
  --muted: #a8b4c3;       /* 次要文字 */
  --accent: #b0453a;      /* 主题色(按钮/链接/高亮) */
  --danger: #d9564c;      /* 错误/危险 */
}

上面七个默认值是 server/cmd/skin-nexus/theme-base.css 里 :root 的抄本, scripts/check-theme-contract.mjs 会逐键比对两者(CI docs job)。真源改了而这里没改, 主题作者就会照着过期色板写包——所以这条闸不许放宽,只能两边一起改。

另有一个应用侧局部变量可覆盖:--fg(局部文字色,消费点自带 inherit 回退,只在少数组件里引用)。 其余 --text-dim 之类的名字没有宿主消费点,覆盖了没有任何效果——只认 §3 列出的契约键与派生键。

注入顺序保证(2026-10-25 起):主题 CSS → 自定义 CSS,且两者都注入在应用编译产物 CSS 之后(同特异性后定义者胜)——所以平实的 :root { --bg: ... } 就能生效。 应用 CSS 必须单 bundle 在 <head> 中先加载;异步页面 chunk 只拆 JS,不再注入页面 CSS (否则后注入的 scoped 样式会在同特异性下压过主题,2026-10-31 azure-glass 管理 dock 回归)。 无需 !important。访客首页(landing)消费独立的落地页 token 命名空间(见文末 「落地页 token 命名空间」专节),主题切换即整站换肤。 变量集以 server/cmd/skin-nexus/theme-base.css 的实际定义为准(AI 生成前可对照最新版)。

color-scheme 也要自己声明(:root { color-scheme: light } 或 dark,与你的 --bg 明暗一致): 插件页的 HTML 由宿主注入本主题的根令牌,color-scheme 是其中被带过去的一条;主题不写它, 插件自己那句 :root { color-scheme: dark } 就留下——浅色主题配深色滚动条与表单控件。

令牌必须写在 :root 或 html 上:宿主只把这两个选择器上的自定义属性(加 color-scheme) 交给插件页,写在 body 或其它选择器上的令牌进不了沙箱文档,那一页会先画插件默认色、 等 JS 桥到达才纠正。反过来,你样式表里的裸元素规则(body{background-image}、h1,h2{font-family}、 input{font-size}、table{}、th,td{})不会漏进插件页——插件的字体与排版由插件自己决定, 这是有意为之,别指望主题能改它们。宿主桥取的九个源令牌是 --bg --panel --border --text --muted --accent --jade --danger --ok(链接取 --jade、成功色取 --ok, 这两个在 §11 的派生令牌里,别忘了覆盖)。

4. 选择器稳定区(安全区) ​

✅ 允许:基于语义类名与 data 属性样式化:

  • 全局元素/类:body、h1/h2/h3、label、input、textarea、select、table、th、td、a、 button(含 button.secondary、button.danger、button.ghost、button.danger-outline)、 .card、.badge、.muted、.error、.success、 .container、.container.wide、.row、.crumb、 .site-footer、.site-footer-links、.site-footer-text、.site-footer-credit
  • 组件类(注意下方特异性说明):.nav-item、.nav-title、.nav-group、.user-shell、.user-side、 .user-main、.admin-nav、.tab、.md(公告 Markdown,内含 :deep(a) / :deep(code) 风格)、 .ali、.avatar-box、.avatar-empty、.danger-text、.cell-input、.uploader、.stat-card、.stat-label、.like-btn

管理后台侧栏的条目本身就是全局 .nav-item(web/src/components/AdminPanel.vue 渲染 class="nav-item",容器才是 .admin-nav)。这里曾经列过一个 .admin-nav-item——宿主代码里 没有这个类,照它写的规则是死块,2026-11-09 已移除。

❌ 禁止:依赖 Vite 构建生成的 hash class(如 index-BlotKZVo)——它们随构建变化,主题会碎。

页脚内容由后台「外观 → 页脚」统一配置(文字 + 友链/备案链接),SSR 与 SPA 使用同一 .site-footer 结构;主题只能样式化,不替代配置数据。

特异性说明:带 data-v-… 的组件内类(scoped 样式)特异性是 (0,2,0),主题同名类选择器 (0,1,0) 打不过——覆盖 .nav-item、.tab、button.ghost 等组件类时需提高特异性(如 .card .tab、html .tab) 或在属性冲突时用 !important;变量覆盖(§3)不受此限。

SSR 皮肤库列表(/skinlib)要额外提特异性:server/cmd/skin-nexus/skinlib_ssr.go 的 ssrListStyle 是随 SSR 区块注在 <body> 里的,排在 <head> 中的主题 <link> 之后。 所以 .ssr-grid a、.ssr-list h1 这类同特异性选择器由宿主胜出,主题必须写成 html .ssr-grid a、html .ssr-list h1(多一个元素选择器)才压得住。只消费令牌(颜色/字号/圆角) 不受影响——那部分宿主本来就写的是 var()。落地页与详情页的内联样式在 <head> 里,没有这个问题。

5. 插槽协议(预留) ​

slots 声明可替换区块,值为 HTML 片段(建议 key 命名 <区域>_<子块>,如 home_hero、footer_note):

json
"slots": { "home_hero": "<h1>欢迎来到我的皮肤站</h1>" }

宿主解析这两个字段但不渲染:写了不报错,也不会改变页面。要改布局或交互请用 admin「外观」的自定义 CSS/JS(§6),要做站内功能区块请用插件 slot(见 plugin-spec.md)。extends(父主题继承)同样解析、不生效。

6. 安全边界与 JS 时机 ​

  • 主题 JS 与 admin 自定义 JS 同一信任面:原样注入、无沙箱——只安装/使用你信任的主题。

  • 主题包不包含任何服务端代码;加载器只读 theme.json + inject 列出的文件 + 平铺资源。

  • 分发主题时请附带源码仓库链接,社区主题建议开源(MIT/GPL 均可)。

  • JS 时机:主题 JS 在 <head> 同步加载,自定义 JS 在 </body> 前内联——两者都在 Vue 应用挂载之前执行,直接 querySelector 应用界面会抓空。要操作界面 DOM 一律包一层:

    js
    document.addEventListener('DOMContentLoaded', () => {
      // 此时应用已挂载,可安全操作 DOM
    });

7. 安装、市场与生效 ​

7.1 主题市场与本地上传 ​

官方主题注册表:https://yimingzhanshen.github.io/skin-nexus-plugins/themes/v1/registry.json (源仓库 themes/<id>/,与插件市场同仓不同路径 + 独立 Ed25519 密钥theme-market-2026-11——一把钥匙泄露不会同时沦陷两个市场)。地址与公钥都编译内置, 零配置即开箱可用;要换源或关闭才需要设环境变量:

环境变量作用
SKIN_THEME_MARKET_URL注册表地址;显式置空 = 关闭市场浏览(上传仍可用)
SKIN_THEME_TRUST_KEYSid:base64pub,... 钉死的验签公钥;显式置空 = 市场安装关闭

管理面板「外观」内置 主题市场(目录 / 安装 / 更新 / 卸载)与 本地主题包上传 (zip ≤16 MiB,两步:检查 → 确认安装)。要点:

  • 市场包四道校验:注册表签名 → 制品 size/sha256 → 包内 bundle.json 验签 → theme.json 结构校验(id 一致、name/version/inject 必填、requires.skin_nexus 满足)。
  • 热生效:安装/卸载即时重扫主题注册表,无需重启;正在使用的主题禁止卸载(先切换)。
  • 未签名本地包 = 管理员信任面(与自定义 JS 同权),需显式勾选风险确认; 安装/卸载/上传仅超级管理员网页会话可操作(X-Admin-Token 拒绝 + 同源 CSRF 闸)。
  • 发布方工具:skin-plugin pack-theme --dir <id>/ --out artifacts --key … --key-id … 与 skin-plugin registry --kind theme …(确定性 zip,同版本字节级不可变)。
  • 打包规范:theme.json 在压缩包根(或单层顶层目录内);资源文件平铺包根、 文件名全小写(同 §1 约束)。

7.2 手工安装 / 切换 / 卸载 / 调试 ​

  • 安装:首选 admin「外观」→ 主题市场安装或本地主题包上传(热生效,无需重启); 或把包目录手工放入 data/themes/<id>/ → 重启服务(手工落盘不触发重扫)→ admin「外观」下拉选中保存即生效(保存即时,无需再重启)。
  • 切换:admin「外观」选其它主题保存;选「无(默认样式)」回到默认。
  • 卸载:admin「外观」→ 主题市场卸载(热生效,正在使用的主题禁止卸载,需先切换); 或手工删除包目录 + 重启;若 active_theme 指向已删除的包,表现为「无主题」(不报错)。
  • 改样式调试:改 CSS 后浏览器可能吃缓存(资源缓存 86400s)——把 theme.json 的 version 递增(链接自动带 ?v= 破缓存)或强制刷新(Ctrl+F5)。
  • 排查清单:① 查看网页源码,确认 <!--skin-theme--> 等标记已被替换(看到 /theme-assets/... 链接即注入成功);② 浏览器 Network 确认 /theme-assets/<id>/<version>/<file> 返回 200(404 = 文件名 不合规则/不在包根);③ DevTools → Computed 看 --bg 等变量是否你的值;④ 深链接(刷新一次 任意页面)确保拿的是注入后的页面;⑤ 还不行看服务日志 theme: skip ... 原因。

8. 高级:自定义 head HTML ​

custom_head_html(admin 外观配置,随自定义 CSS 之后注入 <head>)可贴任意 head 片段 (<link>/<meta>/<script> 等)——能贴什么由本站 CSP 决定,不是由这个字段决定: script-src 'self' 'unsafe-inline'、style-src 'self' 'unsafe-inline'、font-src 'self' data:。 所以内联 <script>/<style> 与站内自托管的 .js/.css/字体都可以(favicon 属于 img-src/文档请求,同样没问题);而 src="https://某个统计站/…" 这种第三方 origins 的脚本、 样式表与字体一律被浏览器拒掉——控制台会报 CSP violation,页面上不留痕迹地不执行。要挂第三方统计, 要么把它的脚本自托管到站点里,要么在反代最外层改 CSP(改法见 deploy.md §3「安全响应头」,两处只配一层)。 适合的场景因此是:自托管资源、结构化数据、<meta>、内联小段脚本。当前无独立输入框, 可经 admin 外观 API(POST /api/admin/v1/appearance,字段 custom_head_html)设置。 与自定义 CSS/JS 同一信任面。

长度上限:自定义 CSS/JS/head HTML 存于数据库 TEXT 列,实用上限约 64KB(请求体上限 1MiB)。

9. 🤖 给 AI 的完整提示词模板(直接粘贴给任意 LLM) ​

请为 SkinNexus 皮肤站生成一个主题包,要求输出两个文件:

1. theme.json —— 遵循以下 schema:
   {"id":"<小写字母数字连字符下划线,如 my-theme>","name":"<中文展示名>","version":"1.0.0",
    "author":"<作者>","description":"<一句话描述>",
    "inject":{"css":["style.css"],"js":[]},"palette":{"primary":"<主色>"}}

2. style.css —— 纯 CSS,无需编译。规则:
   - 优先覆盖 CSS 变量,不要硬编码颜色到具体选择器:
     --bg(页面背景)、--panel(面板/卡片背景)、--border(边框)、--text(主文字)、
     --muted(次要文字)、--accent(主题色)、--danger(错误/危险);
     局部变量 --fg、--text-dim 可选覆盖
   - 只使用语义选择器:.card、.badge、.muted、.error、.success、
     button(含 button.secondary、button.danger)、.ghost、.danger-outline、
     table、input、select、a、h1/h2。禁止使用构建生成的 hash class。
   - 覆盖带 data-v 属性的组件类(.nav-item/.tab/.ghost 等)时提高特异性或用 !important。
   - 风格描述:<把你的需求写这里,例如「暗紫色赛博朋克风、大圆角、柔和阴影」>
   - 保持可读性:正文对比度不低于 4.5:1。
   - 不要引入外部资源;如需字体只用系统字体栈。
   - 尺寸走宿主刻度,别自造一档:字号 `--fs-xs…--fs-2xl`、间距 `--sp-1…--sp-8`、
     圆角 `--r-s/--r-m/--r-l`、层级 `--z-topbar/--z-menu/--z-toast/--z-modal`、
     等宽 `--mono`(id/哈希/时间戳)。一个包里同时出现 11/13/13.5/15/15.5px 就是随手敲的产物。
   - 层次不要画成颜色:`--glow-*` 与 `--shadow*` 保持中性,暗底靠面阶 + 发丝线 + 顶边高光分离;
     循环播放的装饰动画(背景漂移、呼吸光)在系统减少动效外还要自己关。
   - **渐变字的坑**:`background-clip:text` + `-webkit-text-fill-color:transparent` 是一个整体。
     把渐变色板换成实色时必须整块删掉,否则背景实心、文字透明——不报错、不告警,只是看不见。

输出格式:两个代码块分别标注 theme.json 和 style.css,不要额外解释。

安装与生效要点(生成后执行):① 目录名必须等于 theme.json 的 id(全小写); ② 打包成 zip 走 admin「外观」→ 本地主题包上传(热生效、免重启),或放入 data/themes/<id>/ 后重启服务才识别,面板选中保存才生效;③ 改样式后升 version 或强刷缓存;④ 资源文件平铺放包根、全小写;⑤ extends/slots/requires 里前两个当前不生效、requires 生效(写 ">=0.1" 即可);⑥ 如需挂 head 内容(自托管统计、 <meta>、结构化数据),用 custom_head_html(admin 外观 API)而不是主题 JS;第三方 origins 的脚本/样式/字体会被本站 CSP 拒绝,边界见 §8。

生成后:按上方「安装」步骤部署,admin 面板「外观」选择该主题保存即生效。

10. 落地页 token 命名空间 ​

访客落地页(/,服务端渲染)采用自有设计语言「月下仙山」(中国古风:黛青底 / 月白字 / 朱砂主按钮 / 赭石纹线),token 键为:--ink --ink-2 --ink-3 --line --moon--moon-2 --cinnabar --cinnabar-hi --on-cinnabar --ochre --font-serif。

注入顺序不变:主题包与 admin 自定义 CSS 仍在落地页内联样式之后注入,可按上述新键 整体覆盖落地页配色;旧键 --bg/--panel/--text/--accent 不影响落地页。主题若要适配落地页, 覆盖这些新键即可。

SSR 侧不许手抄令牌值(2026-11-09 起为硬要求,此前详情页 404 复制过一份 :root、 落地页手抄过雾色,都属漂移)。落地页与详情页要颜色就消费令牌:雾 var(--fog)、 远山 var(--mtn-far|--mtn-mid|--mtn-near)、错误文字 var(--danger-ink, var(--danger)); 404 页与另两面一样注入 theme-base.css 的令牌块,自身只留排版规则。SVG 里 fill= / stop-color= 这类呈现属性不解析 var(),要跟着主题走就用 class 上色(.mtn-far{fill:var(--mtn-far)}), 这与 SPA 场景板同一写法。<meta name="theme-color"> 必须是字面色值,是令牌块之外唯一 被允许的浏览器色;模板里的字面值只是无覆盖时的默认(有闸钉住它等于 theme-base 的 --bg),页面渲染时注入层会按当前生效的 --bg——主题包与自定义 CSS 按级联顺序覆盖—— 改写这个 meta,所以改 theme-base 的 --bg 只需同步字面值,装了包的站点不用管。

11. v3 全站设计体系(派生令牌速查) ​

默认皮肤为国画色系(黛青底 / 月白字 / 朱砂主操作 / 青瓷链接 / 赭金警示)。 token 架构(主题作者必读):旧 7 契约键(--bg/--panel/--border/--text/--muted/--accent/--danger) 仍是唯一真源——默认值换成国画色而已;新增语义键全部别名到契约键 (--ink/--ink-2/--line/--moon/--moon-2/--cinnabar 等)。主题覆盖旧键时新键自动跟随, 旧主题不需要任何改动即可兼容;新主题建议同时覆盖以下派生键以求完整:

--ink-3(嵌套面/悬停底)· --line-strong(强描线)· --jade(链接/选中/健康)· --jade-hi · --gold(警示)· --cinnabar-hi(主按钮 hover)· --on-cinnabar(主按钮上的字)· --danger-hi · --danger-ink(面板上的错误文字,比 --danger 提亮到 AA)· --ok · --hover-wash(表格行悬停薄雾)· --r-s/--r-m/--r-l(圆角三档,现值 6/10/14)· --shadow/--shadow-lift(层次两层)· --mono(等宽栈,id/哈希/时间戳)· --ease/--t-fast/--t-mid(动效)。

层次不用颜色画:--glow-cinnabar / --glow-danger 现值是中性墨色,--shadow 是 inset 顶边高光——暗底上的分离靠面阶与发丝线。键名保留着(已上架的包在覆盖它们, 改名等于让那些覆盖静默变死变量),但不要把它们填回品牌色彩晕。

v3.3 补充键:派生别名 --panel-2(嵌套面板面,默认 --ink-3;皮肤卡预览/进度槽)与 --accent-ink(主色底文字,默认 --on-cinnabar); 间距刻度 --sp-1…--sp-8(4/8/12/16/24/32/48/64px)与层级刻度 --z-topbar/--z-menu/--z-toast/--z-modal (50/60/70/80)——布局类建议按刻度取值但主题可不覆盖。徽章改走变体类 .badge.danger/.ok/.jade/.gold/.dim,不再用内联 style 改色(主题覆盖 .badge 即联动)。 SSR 落地页/详情页与 SPA 共用同一组字号与动效令牌(--fs-*、--t-*),值漂移视为 bug。 :root 令牌块本体在 server/cmd/skin-nexus/theme-base.css(唯一真源): SPA 经 web/src/style.css @import 引入,SSR 两页 //go:embed 注入 <style> 首段; 主题包覆盖契约键的机制不受影响(注入点仍在 <!--skin-theme--> 之前)。

新结构类(布局契约,主题可覆盖):.topbar(sticky 玻璃顶栏,高 --topbar-h)、 .auth-split/.auth-scene/.auth-pane(登录/注册/找回/重置的分栏场景布局,≤860px 塌缩)、 .user-chip(侧栏底部用户卡)、.nav-item .nav-ico(图标导航,活动态朱砂左条)、 .page-head(页面标题行)。已适配主题:azure-glass / ember-forge / verdigris-atelier (源码在分发仓库 themes/<id>/)。

红线不变:logo(N 字牌)为品牌资产,任何主题不得改其配色;变色须全品牌资产统一并由站主终审。

12. 主题包能力边界 ​

主题拥有布局与排版的完全改造权——主题包的存在意义就是把站点改成完全另一个样子(样板:官方 azure-glass 3.3.0,默认亮色、一条中性阶加一个蓝主色、实面卡片 + 发丝线、玻璃只留 dock 与左右侧栏、 顶部 dock、居中宽幅)。

可以改(全放开):布局方向与栅格(行/列/sticky/宽度/不对称偏移)、排版(字体栈/字阶/字距/渐变字)、色板(契约键 + v3 派生键全量覆盖)、组件形态(圆角/胶囊/玻璃/阴影/边框/斑马纹/发丝线)、背景与装饰、动效时长与曲线、明暗模式策略(如 html.azg-dark 双态)。 这一句是能力清单,不是推荐做法:渐变字与满屏玻璃拟态是 2026-11-09 那轮从官方 azure-glass 里撤掉的写法(撤的理由见包内文件头);宿主不会拦,因为主题包的意义就是敢把站点改样。

自托管像素字体的字号纪律:如果你随包带一款像素/点阵字体并把它放到 --font-serif 这类展示位, 字号必须是该字体栅格的整数倍,否则笔画落在半设备像素上,整片标题发虚。栅格可以从字体文件反推: 用 fontTools 读 unitsPerEm 与 glyf 轮廓坐标的最大公约数——例如 unitsPerEm=1024、拉丁坐标 GCD=128 就是 8×8/em,中文坐标 GCD=64 就是 16×16/em;中英混排要同时满足两者,取最小公倍数(该例为 16px 的倍数)。 因此这类展示位允许写死字面量字号(--fs-* 刻度不一定落在栅格上),但要在包内注释里写明栅格依据; 连续 clamp(min, Nvw, max) 做不到这一点(中间宽度必然落在非整数倍上),请改成断点分档。 标签、徽章、代码与正文仍走阅读面字体并消费 --fs-*——展示字体不上小字号标签。 另注意:cmap 覆盖中文的像素字体不会自动回落黑体,「中文不吃像素」这种假设要按 cmap 实测,别照字体名猜。

不可改(底线四条):

  1. logo 品牌资产:N 字牌配色不动(变色须全品牌资产统一并由站主终审);
  2. 内容语义:不改文案、不伪造状态、不隐藏必要信息(如错误/安全提示);
  3. 注入锚点:<!--skin-seo--> / <!--skin-theme--> / <!--skin-custom-head--> / <!--skin-custom-body--> 结构不动;
  4. 可访问性底线::focus-visible 可见、正文对比度 ≥4.5:1、prefers-reduced-motion 被尊重、不引入仅装饰性 emoji 图标。

共存契约(改结构时的三条工程纪律):① sticky 元素为 v3 顶栏让位——top: calc(var(--topbar-h, 56px) + 间距),z 层级低于顶栏(50);② v3 新结构类(.user-chip/.nav-ico/.auth-split/.drop-zone 等)在主题布局里要有合理位置,不得遗留半吊状态;③ 与全站样式对撞时修共存、不砍结构(逐点修高度/z/间距/换行),不撤销对方的设计意图。

邮件只消费 accent(2026-11-11) ​

站点三封信(找回密码 / 邮箱验证 / 面板测试信)的 HTML 副本在发信那一刻把主题令牌烤成 具体色值(内联样式 + bgcolor),不引用 CSS 变量、不带 <style>、不加载远程图或字体: 邮件客户端剥样式、拦远程内容,Outlook 的 Word 引擎只认 table 与 bgcolor。

  • 跟主题的只有色带与按钮底(--accent);按钮文字色由 accent 亮度派生(亮底深字、暗底浅字)。
  • 正文恒定浅底深字,不跟 --bg/--text:theme-base.css 是暗色真源,而客户端会强制自己的 底色并做不可预测的暗色反转,跟暗色真源等于白底白字。默认 accent 是独立常量,改主题真源不会静默改邮件。
  • 取值走 theme.TokenMap():只取无条件 :root 的 --*(@media (prefers-color-scheme: dark) 里的深色值不进邮件),别名最多解析两跳,环/缺失/alpha<1/8 位 hex/oklch/hsl/color-mix/命名色一律丢弃。
  • 已发出的信是快照:之后换主题不会改旧信,这是邮件的本质而非缺陷。

AGPL-3.0 · SkinNexus