外观方案(皮肤)文件格式
外观方案是一份 JSON(kind: "xinghan.appearance"),既可以在「偏好设置 → 外观」里导入导出,也可以由插件经 xinghan/appearance-presets 贡献点带进来。宿主解析时宽进严出:认不出的字段丢掉并给出提示,不会因为一个字段而整套作废;比宿主新的顶层字段原样收进 _ext,导出时吐回,不会被吞掉。
一份用满全部维度的例子(内置的「洛可可」就是它;「青花」用的是同一组槽位,配色和装饰件反着取):
{ "kind": "xinghan.appearance", "schema": 1, "id": "my-rococo", "name": "洛可可", "colors": { "light": { "accent": "#c9a24c", "surface": "#fbf5e8", "headerStrip": "#e0c06a", "ink": "#3b2d1f", "line": "#d9c391" } }, "shape": { "cardRadius": 16, "chipRadius": 999 }, "background": { "kind": "preset", "preset": "sky" }, "chips": { "tone": "pastel" }, "typography": { "display": "\"Cormorant Garamond\", Georgia, \"Songti SC\", serif", "body": "\"Noto Serif SC\", Georgia, serif", "numerals": "display", "webFonts": ["Cormorant Garamond:ital,wght@0,600;1,600"] }, "frame": { "placement": "window", "panels": "boiserie", "light": { "outer": ["#f6e7b8", "#c9a24c", "#8f6d24"], "inner": "#f6ecc8", "width": 8, "radius": 14, "mat": 18 } }, "ornaments": { "corner": { "kind": "builtin", "id": "rocaille" }, "band": { "kind": "builtin", "id": "cartouche" }, "divider": { "kind": "builtin", "id": "acanthus" }, "edge": { "kind": "builtin", "id": "shell-edge" }, "swag": { "kind": "builtin", "id": "festoon" }, "fleuron": { "kind": "builtin", "id": "fleuron" }, "scale": 0.9 }, "texture": { "where": "wall", "light": { "kind": "builtin", "id": "brocade", "opacity": 0.16 } }, "gradients": { "light": { "accent": ["#f6e7b8", "#e0c06a", "#c9a24c"], "headerStrip": ["#f3e2a6", "#c9a24c"] } }}| 字段 | 管什么 |
|---|---|
colors.light / colors.dark | 表格的八个色位(accent / surface / surfaceAlt / tray / headerStrip / ink / line / ring)、可选的 highlight(选中 / 悬停底色,不给就从 accent 派生)和外壳的一组(shell.*:shell / sidebar / paper / ink / inkMuted / line / popoverSurface / popoverLine,以及侧栏树的 navIcon 图标统一色、sidebarAccent 选中行底)。accent: "inherit" = 跟随用户在偏好里选的主题色。缺 dark 时夜间回落 light。 |
shape | cardRadius(0–16)、chipRadius(0–999)、chipHeight(16–28)、density |
grid | columnBorders / rowBorders,关掉 = 那一档线画成透明 |
rowColor | 行着色策略 accent / soft |
surface | 纸面形态 solid / sheet / open / clear 与玻璃旋钮 blur / rim / contrast,明暗各一套 |
background | 方案建议的壁纸:preset(内置的 aurora / dusk / linen / dots / mesh / sky / vignette / ink-mountains 水墨远山 / auspicious-clouds 祥云 / dome 穹顶 / rose-window 玫瑰窗 / candle 烛光 / mandala 曼陀罗)、url(只放行 https)、data(webp / png / jpeg,≤ 400KB);用户自己选过壁纸时以用户的为准 |
vars | 逃生舱:直接写 --table-* / --canvas-table-* 等命名空间的变量;值仍要过颜色 / 长度校验,写不出 url() |
这五个都是顶层字段,没声明就一个变量都不写。
typography —— 字体
Section titled “typography —— 字体”| 键 | 说明 |
|---|---|
display | 标题类文字的字体栈:格名、项目名、面包屑、列头、题额。永远生效 |
body | 正文字体栈。只在用户字体偏好是「系统」时生效:用户明确选过衬线 / 等宽的,用户的赢 |
mono | 等宽栈(SQL 编辑器等) |
numerals | "display" 让表格里的数字也走 display 栈 |
italicTitles | true 让格名、项目名用斜体(列头不斜) |
nav | "display" 让侧栏树的文件名、分区标题也走 display 栈 |
webFonts | Google Fonts 的 family 规格数组,宿主拼成 fonts.googleapis.com/css2 的链接;离线时退回栈里的系统字体。只放行这一家 |
字体栈只认字母、数字、引号、逗号、连字符(含中文字体名);带括号、分号的整条丢掉。
frame —— 线脚
Section titled “frame —— 线脚”框画在哪由 placement 定,明暗各一套样式:
| 键 | 说明 |
|---|---|
placement | "panes"(默认)每个分栏格 / 经典布局的主文档区各一圈;"window" 顶栏以下的整个内容区一圈,像一幅画的画框,格子退成一道细线,角饰也搬到窗角 |
panels | "plain"(默认)格子是直角矩形;"boiserie" 格子 / 侧栏 / 主文档区四角向内收一个小圆弧(洛可可墙板),细线跟着轮廓走;"begonia" 内凹的弧大一圈、双层窗框(园林的海棠漏窗);"scroll" 四周一圈卷草联珠的纹样当边框、四角团花(敦煌的壁画边饰),格子让出这一圈;"multifoil" / "cusped" 顶边一排圆瓣 / 尖瓣(多叶拱、尖拱窗棂,瓣按整数个重复);"brocade" 四周一圈藏青锦缎(金色菱格小花、外金内朱两道线,唐卡的装裱) |
light / dark | 下面这组 |
| 键 | 说明 |
|---|---|
outer | 外层:一个颜色,或 2–4 个颜色的数组(= 135° 的金属渐变) |
inner | 内侧那道细线的颜色;挂窗沿时也是衬边的底色 |
width | 0–8 px |
radius | 0–32 px。挂窗沿时跟窗角走(macOS 窗角约 10px,直角会被系统切掉一截;14 左右合适) |
mat | 衬边(画框里的卡纸),0–40 px,只在 placement: "window" 时有意义:画框和内容之间留这么宽一圈实色,角饰落在衬边上,不压内容的角 |
ornaments —— 装饰
Section titled “ornaments —— 装饰”| 槽位 | 挂在哪 |
|---|---|
corner | 格子 / 编辑区的角,侧栏右下角;线脚挂窗沿时改挂窗口内容区的四角。内置的 rocaille 自带第二张,右上 / 左下用它,四角不对称 |
edge | 画框上下两条长边的中点(只在挂窗沿时) |
swag | 花彩:挂在画框上沿正中的垂饰,有它时上沿不再放 edge |
fleuron | 花叶符:格名后面、经典顶栏面包屑的分隔处 |
band | 题额:导航项目头下面单独一行牌子托项目名(铺满导航宽度)、经典顶栏里的项目名 |
divider | 侧栏项目头下面的分隔花饰 |
每个槽位一个引用:{ "kind": "builtin", "id": "…" } 用内置库(rocaille 贝壳卷草、cartouche 题额、acanthus 莨苕、deco-corner 装饰艺术直角、seigaiha 青海波、shell-edge 边中贝壳、festoon 花彩、fleuron 花叶符、ruyi 如意云头、begonia 海棠开光题额、fret 回纹、wave-edge 海水纹、lattice-corner 漏窗角、plaque 匾额、tile-ends 瓦当、ice-ray 冰裂窗格、honeysuckle 忍冬卷草、caisson 藻井题额、pearl-string 联珠、lotus-seat 莲花座、apsara-ribbon 飞天飘带、eave 檐口瓦当、ice-line 冰裂线、geo-fleuron / geo-divider 几何圆三角方、tracery-corner / quatrefoil-band / quatrefoil-knot / cross-fleury / trefoil-gable 大教堂五张、muqarnas / zellige / strapwork / eight-star / mosque-lamp 米哈拉布五张、endless-knot / bemma / prayer-wheels / dharma-wheel / wheel-deer-flags 唐卡五张;每张只能挂在它设计的槽位上),或 { "kind": "svg", "svg": "<svg …>" } 自带一张 SVG。自带 SVG ≤ 24KB,且不得含 <script> / <foreignObject> / <image> / <use> / 事件属性 / href / url() / 实体声明 —— 任一处越界整张丢掉。另有 scale(0.5–1.5)与 opacity(0–1)。
chips —— 选项胶囊的调性
Section titled “chips —— 选项胶囊的调性”单选 / 多选胶囊怎么用选项自己的颜色。选项色是数据,方案不改色相,只定明度与彩度上限:{ "tone": "pastel" | "muted" | "vivid" | "tile" }。粉彩是浅底深字、彩度封低(暖色纸面配它),灰调再灰一档,鲜亮是明度钉住、彩度留满;tile 反过来——实色中明度的底、近白的字、比底深一档的缝(釉砖、彩窗、经幡);黄到青那段色相底会抬亮、字换墨,不然是橄榄。不写就跟随纸面形态(开放表格档自带一套,实心纸不处理)。
texture —— 纸面纹理
Section titled “texture —— 纸面纹理”铺在纸面上、内容之下,跟壁纸是两层。where 定铺哪:"paper"(默认)纸面全铺,含表格行底下;"wall" 只铺侧栏和画框衬边,格子与数据行干净(行底下压花纹会读成脏的)。明暗各一套:内置 { "kind": "builtin", "id": "brocade" | "damask" | "lattice" | "linen" | "grain" | "crackle" | "bamboo" | "girih" | "tracery" | "thangka-cloud", "opacity", "scale", "color" },或自带位图 { "kind": "data", "mime": "image/webp" | "image/png", "data": "<base64>", "opacity", "scale" }(≤ 200KB,平铺)。
gradients —— 渐变
Section titled “gradients —— 渐变”明暗各一套,每项 2–4 个颜色:accent(主按钮、选中态)、headerStrip(列头条,两个引擎都画)、shell(经典顶栏)。
插件如何贡献
Section titled “插件如何贡献”在 plugin.json 的 contributions 里声明 xinghan/appearance-presets,data 就是上面这份 JSON(≤ 64KB)。宿主会把 id 改写成 plugin:<pluginId>:<id>,并过同一道校验。插件 iframe 会随 theme/init / theme/update 消息收到 --xh-font-display / --xh-font-body / --xh-font-mono 三个变量;线脚、装饰、纹理不下发给插件。