diff --git a/content/posts/fonts-test.md b/content/posts/fonts-test.md index c3bc4cd..5d7c8a9 100644 --- a/content/posts/fonts-test.md +++ b/content/posts/fonts-test.md @@ -24,8 +24,6 @@ draft: false
弱强调:秋天的海面像一块被风抚平的铁皮。
-霞鹜文楷 Light(自托管 300):秋天的海面像一块被风抚平的铁皮。
-蓝色标准:秋天的海面像一块被风抚平的铁皮。
黄色标准(已压暗):秋天的海面像一块被风抚平的铁皮。
红色 soft:秋天的海面像一块被风抚平的铁皮。
diff --git a/docs/写作指南.md b/docs/写作指南.md index 468788c..90523b0 100644 --- a/docs/写作指南.md +++ b/docs/写作指南.md @@ -88,7 +88,7 @@ https://astro.build/ 正文里可用 `` / `` 做**受限**排版。构建期有白名单过滤:**只放行下表类名,其它 class 与 `style`、`on*` 属性一律被剥离**(防注入 + 保护灰阶骨架)。 -### 颜色类(全部实测亮暗对比度 ≥4.5:1) +### 灰阶层级类(全部实测亮暗对比度 ≥4.5:1) | 类名 | 作用 | |---|---| @@ -100,18 +100,36 @@ https://astro.build/ | `ink-accent` | 强调(链接色,少量点缀) | | `ink-accent-soft` | 弱强调(比强调淡一档) | +### 语义色板(8 色相 × 标准/soft;亮暗各自取值,实测全部 ≥4.5:1) + +| 色相 | 标准类 | soft 类 | 亮态对比度(标准 / soft) | 暗态(标准 / soft) | +|---|---|---|---|---| +| 蓝 | `c-blue` | `c-blue-soft` | 6.46 / 4.58 | 7.70 / 4.61 | +| 青 | `c-teal` | `c-teal-soft` | 5.00 / 4.57 | 9.77 / 4.52 | +| 绿 | `c-green` | `c-green-soft` | 5.38 / 4.67 | 10.38 / 4.67 | +| 黄 | `c-yellow` | `c-yellow-soft` | 6.45 / 4.57 | 11.73 / 4.52 | +| 橙 | `c-orange` | `c-orange-soft` | 6.26 / 4.65 | 9.44 / 4.58 | +| 红 | `c-red` | `c-red-soft` | 6.68 / 4.55 | 7.51 / 4.50 | +| 粉 | `c-pink` | `c-pink-soft` | 6.96 / 4.73 | 8.87 / 4.52 | +| 紫 | `c-purple` | `c-purple-soft` | 7.46 / 4.53 | 8.25 / 4.64 | + +- 黄色在亮态被压暗到 `#7a5900`(原色在白底只有 ~1.8:1),这是**必须**的:彩色文字要能读 +- `soft` 档是"阈值内最淡值",适合次要强调;标准档用于关键强调 + +> **使用纪律(重要)**:彩色**只作用于文章正文的强调文字**。站点外壳(导航、页脚、按钮、背景、卡片边框)一律保持灰阶——整站不因正文用色而花掉。写作时一个页面里的彩色强调建议不超过 3 处。 + ### 字体类 -| 类名 | 作用 | 备注 | -|---|---|---| -| `font-serif` | 思源宋(书评正文族) | 自托管 | -| `font-kai` | 霞鹜文楷(金句族) | 自托管 | -| `font-sans` | 思源黑(UI/技术族) | 自托管 | -| `font-mono` | JetBrains Mono(标签/数值) | 自托管 | -| `font-song` | 宋体族 | ⚠️ 系统字体,**依赖访客设备** | -| `font-kai-sys` | 楷体族 | ⚠️ 系统字体 | -| `font-hei-sys` | 黑体族 | ⚠️ 系统字体 | -| `font-fangsong` | 仿宋族 | ⚠️ 系统字体 | +| 类名 | 作用 | 字体 | 许可证 | 加载 | +|---|---|---|---|---| +| `font-serif` | 书评正文族 | Noto Serif SC | OFL-1.1 | 自托管 | +| `font-kai` | 金句族 | 霞鹜文楷 Screen | OFL-1.1 | 自托管 | +| `font-sans` | UI/技术族 | Noto Sans SC | OFL-1.1 | 自托管 | +| `font-mono` | 标签/数值 | JetBrains Mono | OFL-1.1 | 自托管 | +| `font-song` | 宋体族 | Noto Serif SC 优先,系统宋体兜底 | OFL-1.1 | **自托管优先** | +| `font-kai-sys` | 楷体族 | 霞鹜文楷优先,系统楷体兜底 | OFL-1.1 | **自托管优先** | +| `font-hei-sys` | 黑体族 | Noto Sans SC 优先,系统黑体兜底 | OFL-1.1 | **自托管优先** | +| `font-fangsong` | 仿宋族 | FangSong/STFangsong | — | ⚠️ **纯系统回退**(暂无 OFL 自托管仿宋包) | ### 文学排版类(段落级) @@ -121,7 +139,21 @@ https://astro.build/ | `quote-line` | 金句块(左侧竖线 + 文楷) | | `fig-cap` | 图注(居中小字) | -> **系统字体类的前提**:`-sys` / `font-song` / `font-fangsong` 不下载任何字体文件,命中访客本机字体(macOS/iOS 有宋楷黑、Windows 有宋黑仿宋,Android 常缺楷仿宋)。**跨设备不保证一致**——要稳定请用自托管四族。 +**字体体积账(2026-11-02 实测,本地无 gzip 的相对值)**: + +| 候选 | 许可证 | 引入后实测 | 结论 | +|---|---|---|---| +| 霞鹜文楷 Light/300(`@fontsource/lxgw-wenkai`) | OFL-1.1 | 用到它的页面 **+8605KB** | ❌ 远超 150KB/页 阈值,未采用 | +| 站酷小薇(`@fontsource/zcool-xiaowei`) | OFL-1.1 | 用到它的页面 +648KB | ❌ 同上 | +| 得意黑 Smiley Sans | OFL-1.1 | npm 无可用包(仅 GitHub release 整包,体积更大) | ⏸ 未接入 | +| Sarasa Gothic(含 Mono) | OFL-1.1 | npm 包为空壳(需 cli 自生成切片) | ⏸ 未接入 | +| 汇文明朝体 | 免费商用(非 OFL) | 无 npm 包 | ⏸ 未接入 | +| 现有四族(思源宋/黑、文楷、JetBrains) | OFL-1.1 | 基线:首页 869KB / 书评 3512KB / 技术 875KB | ✅ 已是最优 | + +- 中文 webfont 的代价在 **CJK 切片粒度**:@fontsource 的 CJK 切片刻度是"用几个汉字就拉几 MB",所以新增中文 webfont 单页增量普遍远超 150KB +- 现有四族已覆盖"宋/黑/楷/等宽"四种气质,且全部自托管 → 跨设备一致;`font-song`/`font-kai-sys`/`font-hei-sys` 因**自托管优先**,实际渲染也走站内字体(系统字族只兜底) +- `font-fangsong` 是唯一纯系统回退类:m6 未找到 OFL 自托管仿宋包(Fandol/朱雀等均无 npm 可用包),命中访客本机仿宋字体,**跨设备不保证一致** +- 未用到的字体类在构建期被按页子集化裁掉:**未使用该字体的页面字体字节增量 = 0** ```markdown 次要说明 diff --git a/public/admin/typography-classes.js b/public/admin/typography-classes.js index 7a3214f..db1d378 100644 --- a/public/admin/typography-classes.js +++ b/public/admin/typography-classes.js @@ -1,28 +1,47 @@ /** * 排版类清单 —— 单一事实源(后台编辑器面板与文档共用)。 - * 与 src/styles/global.css 的「受限排版类」段、src/lib/rehype-class-whitelist.ts 的白名单三处保持一致: + * 与 src/styles/global.css 的「受限排版类/语义色板」段、src/lib/rehype-class-whitelist.ts 的白名单三处保持一致: * 加类时三处同改,否则构建期白名单会把新类剥掉。 + * + * swatch / family 仅供**后台面板预览**使用(后台无站点 CSS 变量,故给具体值): + * swatch 取亮态值(后台是浅色 UI),family 为该类的真实字体栈。 */ window.TYPOGRAPHY_CLASSES = { font: [ - { cls: 'font-serif', label: '思源宋(书评正文)' }, - { cls: 'font-kai', label: '霞鹜文楷(金句)' }, - { cls: 'font-sans', label: '思源黑(UI/技术)' }, - { cls: 'font-mono', label: 'JetBrains Mono(等宽)' }, - { cls: 'font-song', label: '宋体(系统)' }, - { cls: 'font-kai-sys', label: '楷体(系统)' }, - { cls: 'font-hei-sys', label: '黑体(系统)' }, - { cls: 'font-fangsong', label: '仿宋(系统)' }, + { cls: 'font-serif', label: '思源宋(书评正文)', family: '"Noto Serif SC", Georgia, serif', kind: '自托管' }, + { cls: 'font-kai', label: '霞鹜文楷(金句)', family: '"LXGW WenKai Screen", "Songti SC", serif', kind: '自托管' }, + { cls: 'font-sans', label: '思源黑(UI/技术)', family: '"Noto Sans SC", system-ui, sans-serif', kind: '自托管' }, + { cls: 'font-mono', label: 'JetBrains Mono(等宽)', family: '"JetBrains Mono", ui-monospace, monospace', kind: '自托管' }, + { cls: 'font-song', label: '宋体族', family: '"Noto Serif SC", "Songti SC", SimSun, serif', kind: '自托管优先' }, + { cls: 'font-kai-sys', label: '楷体族', family: '"LXGW WenKai Screen", "Kaiti SC", STKaiti, serif', kind: '自托管优先' }, + { cls: 'font-hei-sys', label: '黑体族', family: '"Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif', kind: '自托管优先' }, + { cls: 'font-fangsong', label: '仿宋族', family: 'FangSong, STFangsong, serif', kind: '系统回退' }, ], color: [ - { cls: 'ink-1', label: '最重(标题级)' }, - { cls: 'ink-2', label: '正文' }, - { cls: 'ink-3', label: '次要(日期/元信息)' }, - { cls: 'ink-muted', label: '最淡(注释感)' }, - { cls: 'ink-accent', label: '强调(链接色)' }, - { cls: 'ink-accent-soft', label: '弱强调' }, - { cls: 'ink-warm', label: '暖调正文' }, - { cls: 'ink-cool', label: '冷调正文' }, - { cls: 'ink-faint', label: '最淡可读档' }, + { cls: 'ink-1', label: '最重(标题级)', swatch: '#111113' }, + { cls: 'ink-2', label: '正文', swatch: '#33343a' }, + { cls: 'ink-3', label: '次要(日期/元信息)', swatch: '#6b6e78' }, + { cls: 'ink-muted', label: '最淡(注释感)', swatch: '#70747c' }, + { cls: 'ink-warm', label: '暖调正文', swatch: '#3f362e' }, + { cls: 'ink-cool', label: '冷调正文', swatch: '#2f3742' }, + { cls: 'ink-faint', label: '最淡可读档', swatch: '#70747c' }, + { cls: 'ink-accent', label: '强调(链接色)', swatch: '#3454d1' }, + { cls: 'ink-accent-soft', label: '弱强调', swatch: '#4f66c2' }, + { cls: 'c-blue', label: '蓝', swatch: '#2456c9' }, + { cls: 'c-blue-soft', label: '蓝(soft)', swatch: '#4170dd' }, + { cls: 'c-teal', label: '青', swatch: '#0d7d74' }, + { cls: 'c-teal-soft', label: '青(soft)', swatch: '#0e847a' }, + { cls: 'c-green', label: '绿', swatch: '#1f7a3a' }, + { cls: 'c-green-soft', label: '绿(soft)', swatch: '#22853f' }, + { cls: 'c-yellow', label: '黄(已压暗)', swatch: '#7a5900' }, + { cls: 'c-yellow-soft', label: '黄(soft)', swatch: '#926a00' }, + { cls: 'c-orange', label: '橙', swatch: '#9a4a00' }, + { cls: 'c-orange-soft', label: '橙(soft)', swatch: '#ba5900' }, + { cls: 'c-red', label: '红', swatch: '#b32020' }, + { cls: 'c-red-soft', label: '红(soft)', swatch: '#db3737' }, + { cls: 'c-pink', label: '粉', swatch: '#a3246a' }, + { cls: 'c-pink-soft', label: '粉(soft)', swatch: '#ce3386' }, + { cls: 'c-purple', label: '紫', swatch: '#6a35b8' }, + { cls: 'c-purple-soft', label: '紫(soft)', swatch: '#8c5ed1' }, ], }; diff --git a/public/admin/typography-editor.js b/public/admin/typography-editor.js index fba259d..f75a3c9 100644 --- a/public/admin/typography-editor.js +++ b/public/admin/typography-editor.js @@ -37,7 +37,7 @@ card.appendChild(title); const buttons = []; - const addGroup = (name, list) => { + const addGroup = (name, list, withSwatch) => { const h = document.createElement('p'); h.textContent = name; Object.assign(h.style, { margin: '12px 0 6px', fontSize: '12px', color: '#6b6e78' }); @@ -47,22 +47,45 @@ for (const item of list) { const b = document.createElement('button'); b.type = 'button'; - b.textContent = item.label; b.dataset.cls = item.cls; b.className = item.cls === current ? 'active' : ''; Object.assign(b.style, { + display: 'inline-flex', alignItems: 'center', gap: '8px', padding: '6px 12px', borderRadius: '8px', cursor: 'pointer', border: '1px solid ' + (item.cls === current ? '#3454d1' : '#e6e7eb'), background: item.cls === current ? '#eef1fd' : '#fff', color: '#111113', fontSize: '13px', }); + if (withSwatch) { + // 色块预览:直接展示该类在亮态下的实际颜色 + const chip = document.createElement('span'); + Object.assign(chip.style, { + width: '16px', height: '16px', borderRadius: '4px', flex: 'none', + background: item.swatch || '#ccc', + border: '1px solid rgba(0,0,0,.12)', + }); + chip.setAttribute('aria-hidden', 'true'); + b.appendChild(chip); + } else { + // 字体项:名称 + 用该字体栈渲染的示例字形 + const sample = document.createElement('span'); + sample.textContent = '秋海'; + Object.assign(sample.style, { + fontFamily: item.family || 'inherit', fontSize: '15px', lineHeight: '1', + padding: '0 4px', borderLeft: '1px solid #e6e7eb', color: '#33343a', + }); + b.appendChild(sample); + } + const label = document.createElement('span'); + label.textContent = item.label + (item.kind ? `(${item.kind})` : ''); + b.appendChild(label); row.appendChild(b); buttons.push(b); } card.appendChild(row); }; - addGroup('字体', CLASSES.font); - addGroup('颜色', CLASSES.color); + addGroup('字体', CLASSES.font, false); + addGroup('颜色', CLASSES.color, true); const foot = document.createElement('div'); Object.assign(foot.style, { marginTop: '16px', display: 'flex', justifyContent: 'flex-end', gap: '8px' }); diff --git a/src/lib/rehype-class-whitelist.ts b/src/lib/rehype-class-whitelist.ts index 0360a45..35729a3 100644 --- a/src/lib/rehype-class-whitelist.ts +++ b/src/lib/rehype-class-whitelist.ts @@ -44,7 +44,6 @@ const ALLOWED = new Set([ 'font-kai-sys', 'font-hei-sys', 'font-fangsong', - 'font-kai-light', // 文学排版 'drop-cap', 'quote-line', diff --git a/src/styles/global.css b/src/styles/global.css index 0d50160..4404c03 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -213,11 +213,6 @@ pre code { background: none; border: none; padding: 0; font-size: inherit; } .prose-reading .font-fangsong, .prose-tech .font-fangsong { font-family: FangSong, STFangsong, "FangSong_GB2312", serif; } -/* 霞鹜文楷 Light(自托管 @fontsource,OFL-1.1):更轻的金句字重 */ -.prose-reading .font-kai-light, .prose-tech .font-kai-light { - font-family: 'LXGW WenKai', var(--font-kai); - font-weight: 300; -} /* 文学常用:首字下沉(书评开篇)与金句块(引文式的独立强调段) */ .prose-reading p.drop-cap::first-letter, .prose-tech p.drop-cap::first-letter { float: left; diff --git a/src/styles/tokens.css b/src/styles/tokens.css index 6120243..6506c24 100644 --- a/src/styles/tokens.css +++ b/src/styles/tokens.css @@ -32,10 +32,10 @@ --c-blue-soft: #4170dd; --c-teal-soft: #0e847a; --c-green-soft: #22853f; - --c-yellow-soft: #997000; + --c-yellow-soft: #926a00; --c-orange-soft: #ba5900; --c-red-soft: #db3737; - --c-pink-soft: #d3368c; + --c-pink-soft: #ce3386; --c-purple-soft: #8c5ed1; --line: #e6e7eb; /* 分隔线 */