fix(fonts): 字体切换真加载——按内容诉求注入,含瞬间排版类作用域缺口(5i T1)
用户实测「字族渲染没有成功,尤其是字体的切换」。根因不是字体没做,是加载门禁错:
BaseLayout 把 serif/kai/kuaile/hwmc 四份字体 CSS 全关在 `track === 'reading'` 分支里,
技术轨文章与全站页面(默认 track='tech')从不加载它们——于是 `.font-kai` 计算值虽是
`"LXGW WenKai Screen"`,但该 @font-face 根本不在文档字体集里,浏览器静默回退系统宋体。
- 新增 `src/lib/font-needs.ts`:`fontsFromText()` 纯函数 + `fontsForPage(pathname, track)`
按路径取该页内容(文章正文 / home.yaml / about.yaml / opensource.yaml / 全部瞬间文本)
与页面静态诉求(reading→serif;书架标题写死楷体→kai 等)算出所需字族集合
- BaseLayout 改为逐项按需注入(非阻塞 preload+onload 写法保留),noscript 同步;
汇文明朝仍走 public 手工切片路径,network/font-display 未动
- **顺手补掉 5h 遗留的用户可见缺口**:瞬间富文本容器 class 是 `mom-text`,而全局排版类
规则只覆盖 `.prose-*/.rich/.prose-body` → 瞬间里写 `font-kai`/`c-teal` 一直只有 class
没有样式(新瞬间正是这么写的,用户看到的就是"没变色没换字")。global.css 给 38 处
选择器组补 `.mom-text`
- 另修自写 bug:en 路由切分 `slice(3)` 残留前导 `/` 导致 en 页按需字体全部落空,改 `slice(4)`
验证: 构建 exit 0(content-lint 51 页 0/0/0);E2E 46/46;Playwright 实测
技术轨文章 document.fonts 命中 LXGW WenKai Screen / WF-Hwmc / ZCOOL KuaiLe /
Noto Serif SC,`.font-kai` 计算族 = LXGW 且 check=true;
首页仍为基线(零增量);瞬间页因有 font-kai 内容而命中 LXGW;
书评页只拉 serif(41 片,0 kai 0 汇文)
Spec: 2026-11-02_blog-5i-fonts-polish (T1)
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
029633f00f
commit
8d82aab213
4 files changed
+247
-48
No files matched your search
@@ -1,3 +1,14 @@
|
||||
# 打字机动效文案(档案卡顶部逐字轮播;每行双语,最多 6 行;留空用内置默认三行)
|
||||
typing:
|
||||
- zh: 气象出身,写代码为乐
|
||||
en: Trained in meteorology, coding for fun
|
||||
- zh: 自托管一切能自托管的东西
|
||||
en: Self-hosting whatever can be self-hosted
|
||||
- zh: 拥抱开源,热爱开源
|
||||
en: Embrace open source, love open source
|
||||
- zh: 把读过的书、按下的快门都放进同一个工作台
|
||||
en: Books read and shutters pressed, gathered in one workbench
|
||||
|
||||
blurb:
|
||||
zh: 本站本身即开源:
|
||||
en: 'This site is itself open source:'
|
||||
|
||||
@@ -15,6 +15,7 @@ import ThemeScript from '../components/ThemeScript.astro';
|
||||
import Keyboard from '../components/geek/Keyboard.astro';
|
||||
import { localeOf, localePath } from '../lib/locale';
|
||||
import { siteFavicon } from '../lib/site-content';
|
||||
import { fontsForPage } from '../lib/font-needs';
|
||||
import SearchDialog from '../components/SearchDialog.astro';
|
||||
import BuildInfo from '../components/geek/BuildInfo.astro';
|
||||
import { ClientRouter } from 'astro:transitions';
|
||||
@@ -38,6 +39,10 @@ const favicon = await siteFavicon();
|
||||
// 自指 canonical(T2):SEO 权重收敛到当前页自身,与 hreflang 组合覆盖双轨
|
||||
const canonicalUrl = new URL(withSlash(rawPath), Astro.site).href;
|
||||
const themeStyle = await themeCss();
|
||||
// 字体按需加载(Phase 5i T1):不再按 track 一刀切——旧实现把 serif/kai/kuaile/汇文
|
||||
// 全关在 reading 分支里,技术轨用 .font-kai 时字体 CSS 从不加载、浏览器静默回退系统宋体。
|
||||
// 现改为:本页内容里扫到什么字族就加载什么(详见 src/lib/font-needs.ts 头注释)。
|
||||
const fontNeeds = await fontsForPage(rawPath, track);
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
@@ -70,22 +75,31 @@ const themeStyle = await themeCss();
|
||||
</script>
|
||||
<ClientRouter />
|
||||
<!-- 非阻塞字体 CSS:preload+onload 回切(Lighthouse P0-1:渲染阻塞 708KB 元凶) -->
|
||||
<!-- 常驻:无衬线正文 + 代码 mono;按需:serif/kai/kuaile/汇文——取决于本页内容诉求 -->
|
||||
<link rel="preload" as="style" href={sansVar} onload="this.rel='stylesheet'" />
|
||||
<link rel="preload" as="style" href={mono400} onload="this.rel='stylesheet'" />
|
||||
<link rel="preload" as="style" href={mono700} onload="this.rel='stylesheet'" />
|
||||
{track === 'reading' && (
|
||||
<>
|
||||
<link rel="preload" as="style" href={serifVar} onload="this.rel='stylesheet'" />
|
||||
<link rel="preload" as="style" href={kai} onload="this.rel='stylesheet'" />
|
||||
<link rel="preload" as="style" href={kuaile} onload="this.rel='stylesheet'" />
|
||||
<!-- 汇文明朝(OFL-1.1,文风开源切片包 339 片,按 unicode-range 按需拉取) -->
|
||||
<link rel="preload" as="style" href="/fonts/subset/hwmc/fonts.css" onload="this.rel='stylesheet'" />
|
||||
</>
|
||||
{fontNeeds.has('serif') && (
|
||||
<link rel="preload" as="style" href={serifVar} onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
{fontNeeds.has('kai') && (
|
||||
<link rel="preload" as="style" href={kai} onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
{fontNeeds.has('kuaile') && (
|
||||
<link rel="preload" as="style" href={kuaile} onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
<!-- 汇文明朝(OFL-1.1,文风开源切片包 339 片,按 unicode-range 按需拉取;
|
||||
手工切片产物路径固定,勿动) -->
|
||||
{fontNeeds.has('huiwen') && (
|
||||
<link rel="preload" as="style" href="/fonts/subset/hwmc/fonts.css" onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
<noscript>
|
||||
<link rel="stylesheet" href={sansVar} />
|
||||
<link rel="stylesheet" href={mono400} />
|
||||
{track === 'reading' && <link rel="stylesheet" href={serifVar} />}
|
||||
{fontNeeds.has('serif') && <link rel="stylesheet" href={serifVar} />}
|
||||
{fontNeeds.has('kai') && <link rel="stylesheet" href={kai} />}
|
||||
{fontNeeds.has('kuaile') && <link rel="stylesheet" href={kuaile} />}
|
||||
{fontNeeds.has('huiwen') && <link rel="stylesheet" href="/fonts/subset/hwmc/fonts.css" />}
|
||||
</noscript>
|
||||
<!-- PWA:manifest + 双方案 theme-color(明暗随系统)+ iOS 图标 -->
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* 字体按需加载(Phase 5i T1):把「轨道门禁」换成「内容诉求驱动」。
|
||||
*
|
||||
* ## 为什么要有这个文件(2026-11-02 用户实测踩的坑,勿删)
|
||||
*
|
||||
* 旧实现把 serif / kai / kuaile / 汇文四份字体 CSS 全关在
|
||||
* `track === 'reading'` 分支里(BaseLayout)。但技术轨才是全站默认
|
||||
* (`track` 缺省 `tech`),于是出现一类**最隐蔽的失效**:
|
||||
*
|
||||
* - 技术文里写 `<span class="font-kai">`:class 在、`font-family` 计算值也对
|
||||
* (`"LXGW WenKai Screen", …`),但那份字体 CSS 从未加载 →
|
||||
* `document.fonts` 里根本没有这个族 → 浏览器静默回退系统宋体。
|
||||
* - 它不报错、不报警,用户看到的现象只有一句:「字体没换」。
|
||||
*
|
||||
* 教训:字体是「外部资源 + 静默回退」,门禁不能按页面所属栏目拍脑袋,
|
||||
* 只能看这一页**真正声明的诉求**——扫出来的字族才加载,扫不到就不加载。
|
||||
*
|
||||
* ## 本文件的两类输入
|
||||
*
|
||||
* 1. **内容扫描**:正文 markdown、站点单例 yaml(页面文案/块)、瞬间文本里
|
||||
* 出现的类名(含 renderRich 白名单放行的排版类)。
|
||||
* 2. **页面静态需求**:某些页面把字族写死在组件里(书架卡标题用楷体、
|
||||
* 关于页的 quote 块用 `.quote-line` 楷体),内容里扫不到类名,也要算。
|
||||
*
|
||||
* @module font-needs
|
||||
*/
|
||||
import { getCollection, getEntry } from 'astro:content';
|
||||
|
||||
/** 可独立加载的字体 CSS 包(sans / mono 常驻,不在此列) */
|
||||
export type FontKey = 'serif' | 'kai' | 'kuaile' | 'huiwen';
|
||||
|
||||
/**
|
||||
* 类名 → 需要的字体包。
|
||||
*
|
||||
* 只列「会让浏览器拉 webfont」的类;以下三类**不产生**字体请求,故不入表:
|
||||
* - `font-hei-sys`:首选 Noto Sans SC(常驻已加载)
|
||||
* - `font-fangsong`:纯系统回退(无 OFL 自托管包,docs 已注明)
|
||||
* - 普通 `font-sans` / `font-mono`:常驻
|
||||
*
|
||||
* 另外把两个「文学排版类」也计入,因为它们各自改字体:
|
||||
* `quote-line` → 楷体;`drop-cap` → 首字衬线。
|
||||
*/
|
||||
export const FONT_CLASS_NEEDS: Record<string, FontKey[]> = {
|
||||
'font-serif': ['serif'],
|
||||
'font-song': ['serif'], // 首选 "Noto Serif SC Variable"(= serif 包)
|
||||
'font-kai': ['kai'],
|
||||
'font-kai-sys': ['kai'], // 首选 "LXGW WenKai Screen"(= kai 包)
|
||||
'font-huiwen': ['huiwen'],
|
||||
'font-kuaile': ['kuaile'],
|
||||
'font-art-kuaile': ['kuaile'], // 同一个 ZCOOL KuaiLe 族
|
||||
'quote-line': ['kai'],
|
||||
'drop-cap': ['serif'],
|
||||
};
|
||||
|
||||
/** 拼接成一条正则:`\bfont-kai\b` 这类词边界匹配(避免 font-kuaile 误配 font-kuaile-x) */
|
||||
const CLASS_PATTERN = new RegExp(
|
||||
`\\b(?:${Object.keys(FONT_CLASS_NEEDS)
|
||||
.map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
||||
.join('|')})\\b`,
|
||||
'g',
|
||||
);
|
||||
|
||||
/**
|
||||
* 从若干段文本里探测需要加载的字体包(纯函数,无 IO)。
|
||||
*
|
||||
* @param texts 页面上可能出现排版类的原始文本(正文 markdown / yaml 序列化 / 瞬间文本等)
|
||||
* @returns 命中的字体包集合;空集表示本页只需常驻的 sans + mono
|
||||
*
|
||||
* @example
|
||||
* fontsFromText(['<span class="font-kai">字</span>']); // Set { 'kai' }
|
||||
* fontsFromText(['纯文本没有类名']); // Set {}
|
||||
*/
|
||||
export function fontsFromText(texts: (string | undefined | null)[]): Set<FontKey> {
|
||||
const out = new Set<FontKey>();
|
||||
for (const text of texts) {
|
||||
if (!text) continue;
|
||||
for (const hit of text.matchAll(CLASS_PATTERN)) {
|
||||
for (const key of FONT_CLASS_NEEDS[hit[0]] ?? []) out.add(key);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 把 yaml 单例的 data 摊平成可扫描文本(序列化即可:类名都是字符串字面量) */
|
||||
const flatten = (data: unknown): string => {
|
||||
try {
|
||||
return JSON.stringify(data);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/** 路径归一:去 query/hash、去 index.html、保留结尾斜杠,便于前缀匹配 */
|
||||
const normalizePath = (pathname: string): string => {
|
||||
const p = pathname.split(/[?#]/)[0].replace(/\/index\.html$/, '/').replace(/\.html$/, '/');
|
||||
const withSlash = p.endsWith('/') ? p : `${p}/`;
|
||||
return withSlash.startsWith('/') ? withSlash : `/${withSlash}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析一个页面需要哪些字体包(构建期,供 BaseLayout 注入)。
|
||||
*
|
||||
* 规则 = **页面静态需求 ∪ 内容扫描结果**:
|
||||
* - 书评轨(`track='reading'`)正文默认字族就是衬线 → serif 必载;
|
||||
* - 书架页标题写死楷体、英文书架写死衬线 → 该两页固定 kai(+en 加 serif);
|
||||
* - 关于页的 quote 块写死 `.quote-line` → 有 quote 块即需 kai;
|
||||
* - 其余全部靠内容扫描(文章正文 / 页面单例 yaml / 瞬间文本)。
|
||||
*
|
||||
* 未识别的页面返回空集:只加载常驻 sans + mono,绝不凭栏目名猜字体。
|
||||
*
|
||||
* @param pathname 当前页面路径(`Astro.url.pathname`)
|
||||
* @param track 正文轨(只影响书评默认衬线这一条静态需求)
|
||||
* @returns 需要加载的字体包集合
|
||||
*/
|
||||
export async function fontsForPage(pathname: string, track: 'reading' | 'tech' = 'tech'): Promise<Set<FontKey>> {
|
||||
const path = normalizePath(pathname);
|
||||
const isEn = path.startsWith('/en/');
|
||||
// 去掉语言前缀与开头斜杠,得到形如 'archives/x/' 的路由键。
|
||||
// 注意 slice(4)('/en/' 四个字符)而不是 slice(3):后者会残留一个前导 '/',
|
||||
// 导致 en 页全部匹配不上(2026-11-03 实测:en/moments、en/archives/* 的按需字体全丢)。
|
||||
const route = isEn ? path.slice(4) : path.slice(1);
|
||||
const needs = new Set<FontKey>();
|
||||
|
||||
// ① 书评轨:正文默认字体就是衬线(`.prose-reading { font-family: var(--font-serif) }`)
|
||||
if (track === 'reading') needs.add('serif');
|
||||
|
||||
// ② 文章页:扫正文 markdown(中文缺译时回退中文正文,与页面的回落一致)
|
||||
const article = route.match(/^archives\/([^/]+)\/$/);
|
||||
if (article) {
|
||||
const slug = article[1];
|
||||
const enEntry = isEn ? await getEntry('postsEn', slug) : undefined;
|
||||
const entry = enEntry ?? (await getEntry('posts', slug));
|
||||
const texts = [entry?.body ?? ''];
|
||||
for (const key of fontsFromText(texts)) needs.add(key);
|
||||
return needs;
|
||||
}
|
||||
|
||||
// ③ 书架页:书脊卡标题写死楷体(BooksPage.astro),英文书架再写死衬线
|
||||
if (route === 'books/') {
|
||||
needs.add('kai');
|
||||
if (isEn) needs.add('serif');
|
||||
return needs;
|
||||
}
|
||||
|
||||
// ④ 首页 / 关于页 / 开源页:扫各自单例(页面文案与块内容都走 renderRich 白名单)
|
||||
if (route === '') {
|
||||
const home = await getEntry('siteHome', 'home');
|
||||
for (const key of fontsFromText([flatten(home?.data)])) needs.add(key);
|
||||
return needs;
|
||||
}
|
||||
if (route === 'about/') {
|
||||
const about = await getEntry('siteAbout', 'about');
|
||||
const text = flatten(about?.data);
|
||||
for (const key of fontsFromText([text])) needs.add(key);
|
||||
// quote 块在组件里写死 `.quote-line`(楷体),内容里没有类名字面量 → 按块型补
|
||||
if (text.includes('"type":"quote"')) needs.add('kai');
|
||||
return needs;
|
||||
}
|
||||
if (route === 'opensource/') {
|
||||
const os = await getEntry('siteOpenSource', 'opensource');
|
||||
for (const key of fontsFromText([flatten(os?.data)])) needs.add(key);
|
||||
return needs;
|
||||
}
|
||||
|
||||
// ⑤ 瞬间页:T2 起瞬间正文走富文本管线,可能带排版类 → 扫全部瞬间文本
|
||||
if (route === 'moments/') {
|
||||
const moments = await getCollection('moments');
|
||||
for (const key of fontsFromText(moments.map((m) => m.data.text))) needs.add(key);
|
||||
return needs;
|
||||
}
|
||||
|
||||
// ⑥ 其余页面(归档列表 / 图库 / 终端 / 栈 / 纸面…):只吃常驻 sans + mono
|
||||
return needs;
|
||||
}
|
||||
+39
-39
@@ -148,7 +148,7 @@ pre code { background: none; border: none; padding: 0; font-size: inherit; }
|
||||
}
|
||||
/* 图注:**显式类驱动**(`<p class="fig-cap">说明</p>`)。此前按 `p > em:only-child` 自动命中,
|
||||
把正文里任何独占成段的斜体都变成块级居中——用户实测的排版 bug(斜体单独一行并居中)由此而来。 */
|
||||
.prose-reading p.fig-cap, .prose-tech p.fig-cap, .rich p.fig-cap, .prose-body p.fig-cap,
|
||||
.prose-reading p.fig-cap, .prose-tech p.fig-cap, .rich p.fig-cap, .prose-body p.fig-cap, .mom-text p.fig-cap,
|
||||
.prose-reading p.fig-cap em, .prose-tech p.fig-cap em {
|
||||
display: block;
|
||||
text-align: center;
|
||||
@@ -167,66 +167,66 @@ pre code { background: none; border: none; padding: 0; font-size: inherit; }
|
||||
}
|
||||
/* ── 受限排版类(文章内通过 <span class> 使用;rehype 白名单只放行这些类名) ──
|
||||
颜色一律走 tokens,亮暗双态都可读;禁任意 hex/内联样式(保持灰阶骨架)。 */
|
||||
.prose-reading .ink-1, .prose-tech .ink-1, .rich .ink-1, .prose-body .ink-1 { color: var(--ink-1); }
|
||||
.prose-reading .ink-2, .prose-tech .ink-2, .rich .ink-2, .prose-body .ink-2 { color: var(--ink-2); }
|
||||
.prose-reading .ink-3, .prose-tech .ink-3, .rich .ink-3, .prose-body .ink-3 { color: var(--ink-3); }
|
||||
.prose-reading .ink-muted, .prose-tech .ink-muted, .rich .ink-muted, .prose-body .ink-muted { color: var(--ink-4); }
|
||||
.prose-reading .ink-accent, .prose-tech .ink-accent, .rich .ink-accent, .prose-body .ink-accent { color: var(--accent); }
|
||||
.prose-reading .ink-accent-soft, .prose-tech .ink-accent-soft, .rich .ink-accent-soft, .prose-body .ink-accent-soft { color: var(--accent-soft-text); }
|
||||
.prose-reading .ink-warm, .prose-tech .ink-warm, .rich .ink-warm, .prose-body .ink-warm { color: var(--ink-warm); }
|
||||
.prose-reading .ink-cool, .prose-tech .ink-cool, .rich .ink-cool, .prose-body .ink-cool { color: var(--ink-cool); }
|
||||
.prose-reading .ink-faint, .prose-tech .ink-faint, .rich .ink-faint, .prose-body .ink-faint { color: var(--ink-faint); }
|
||||
.prose-reading .ink-1, .prose-tech .ink-1, .rich .ink-1, .mom-text .ink-1, .prose-body .ink-1 { color: var(--ink-1); }
|
||||
.prose-reading .ink-2, .prose-tech .ink-2, .rich .ink-2, .mom-text .ink-2, .prose-body .ink-2 { color: var(--ink-2); }
|
||||
.prose-reading .ink-3, .prose-tech .ink-3, .rich .ink-3, .mom-text .ink-3, .prose-body .ink-3 { color: var(--ink-3); }
|
||||
.prose-reading .ink-muted, .prose-tech .ink-muted, .rich .ink-muted, .mom-text .ink-muted, .prose-body .ink-muted { color: var(--ink-4); }
|
||||
.prose-reading .ink-accent, .prose-tech .ink-accent, .rich .ink-accent, .mom-text .ink-accent, .prose-body .ink-accent { color: var(--accent); }
|
||||
.prose-reading .ink-accent-soft, .prose-tech .ink-accent-soft, .rich .ink-accent-soft, .mom-text .ink-accent-soft, .prose-body .ink-accent-soft { color: var(--accent-soft-text); }
|
||||
.prose-reading .ink-warm, .prose-tech .ink-warm, .rich .ink-warm, .mom-text .ink-warm, .prose-body .ink-warm { color: var(--ink-warm); }
|
||||
.prose-reading .ink-cool, .prose-tech .ink-cool, .rich .ink-cool, .mom-text .ink-cool, .prose-body .ink-cool { color: var(--ink-cool); }
|
||||
.prose-reading .ink-faint, .prose-tech .ink-faint, .rich .ink-faint, .mom-text .ink-faint, .prose-body .ink-faint { color: var(--ink-faint); }
|
||||
/* 语义色板:彩色只作用于正文强调(站点外壳保持灰阶)。
|
||||
每色相两档:标准 / soft(阈值内最淡);亮暗各取独立值,均实测 ≥4.5:1。 */
|
||||
.prose-reading .c-blue, .prose-tech .c-blue, .rich .c-blue, .prose-body .c-blue { color: var(--c-blue); }
|
||||
.prose-reading .c-blue-soft, .prose-tech .c-blue-soft, .rich .c-blue-soft, .prose-body .c-blue-soft { color: var(--c-blue-soft); }
|
||||
.prose-reading .c-teal, .prose-tech .c-teal, .rich .c-teal, .prose-body .c-teal { color: var(--c-teal); }
|
||||
.prose-reading .c-teal-soft, .prose-tech .c-teal-soft, .rich .c-teal-soft, .prose-body .c-teal-soft { color: var(--c-teal-soft); }
|
||||
.prose-reading .c-green, .prose-tech .c-green, .rich .c-green, .prose-body .c-green { color: var(--c-green); }
|
||||
.prose-reading .c-green-soft, .prose-tech .c-green-soft, .rich .c-green-soft, .prose-body .c-green-soft { color: var(--c-green-soft); }
|
||||
.prose-reading .c-yellow, .prose-tech .c-yellow, .rich .c-yellow, .prose-body .c-yellow { color: var(--c-yellow); }
|
||||
.prose-reading .c-yellow-soft, .prose-tech .c-yellow-soft, .rich .c-yellow-soft, .prose-body .c-yellow-soft { color: var(--c-yellow-soft); }
|
||||
.prose-reading .c-orange, .prose-tech .c-orange, .rich .c-orange, .prose-body .c-orange { color: var(--c-orange); }
|
||||
.prose-reading .c-orange-soft, .prose-tech .c-orange-soft, .rich .c-orange-soft, .prose-body .c-orange-soft { color: var(--c-orange-soft); }
|
||||
.prose-reading .c-red, .prose-tech .c-red, .rich .c-red, .prose-body .c-red { color: var(--c-red); }
|
||||
.prose-reading .c-red-soft, .prose-tech .c-red-soft, .rich .c-red-soft, .prose-body .c-red-soft { color: var(--c-red-soft); }
|
||||
.prose-reading .c-pink, .prose-tech .c-pink, .rich .c-pink, .prose-body .c-pink { color: var(--c-pink); }
|
||||
.prose-reading .c-pink-soft, .prose-tech .c-pink-soft, .rich .c-pink-soft, .prose-body .c-pink-soft { color: var(--c-pink-soft); }
|
||||
.prose-reading .c-purple, .prose-tech .c-purple, .rich .c-purple, .prose-body .c-purple { color: var(--c-purple); }
|
||||
.prose-reading .c-purple-soft, .prose-tech .c-purple-soft, .rich .c-purple-soft, .prose-body .c-purple-soft { color: var(--c-purple-soft); }
|
||||
.prose-reading .font-serif, .prose-tech .font-serif, .rich .font-serif, .prose-body .font-serif { font-family: var(--font-serif); }
|
||||
.prose-reading .font-kai, .prose-tech .font-kai, .rich .font-kai, .prose-body .font-kai { font-family: var(--font-kai); }
|
||||
.prose-reading .font-sans, .prose-tech .font-sans, .rich .font-sans, .prose-body .font-sans { font-family: var(--font-sans); }
|
||||
.prose-reading .font-mono, .prose-tech .font-mono, .rich .font-mono, .prose-body .font-mono { font-family: var(--font-mono); font-size: 0.92em; }
|
||||
.prose-reading .c-blue, .prose-tech .c-blue, .rich .c-blue, .mom-text .c-blue, .prose-body .c-blue { color: var(--c-blue); }
|
||||
.prose-reading .c-blue-soft, .prose-tech .c-blue-soft, .rich .c-blue-soft, .mom-text .c-blue-soft, .prose-body .c-blue-soft { color: var(--c-blue-soft); }
|
||||
.prose-reading .c-teal, .prose-tech .c-teal, .rich .c-teal, .mom-text .c-teal, .prose-body .c-teal { color: var(--c-teal); }
|
||||
.prose-reading .c-teal-soft, .prose-tech .c-teal-soft, .rich .c-teal-soft, .mom-text .c-teal-soft, .prose-body .c-teal-soft { color: var(--c-teal-soft); }
|
||||
.prose-reading .c-green, .prose-tech .c-green, .rich .c-green, .mom-text .c-green, .prose-body .c-green { color: var(--c-green); }
|
||||
.prose-reading .c-green-soft, .prose-tech .c-green-soft, .rich .c-green-soft, .mom-text .c-green-soft, .prose-body .c-green-soft { color: var(--c-green-soft); }
|
||||
.prose-reading .c-yellow, .prose-tech .c-yellow, .rich .c-yellow, .mom-text .c-yellow, .prose-body .c-yellow { color: var(--c-yellow); }
|
||||
.prose-reading .c-yellow-soft, .prose-tech .c-yellow-soft, .rich .c-yellow-soft, .mom-text .c-yellow-soft, .prose-body .c-yellow-soft { color: var(--c-yellow-soft); }
|
||||
.prose-reading .c-orange, .prose-tech .c-orange, .rich .c-orange, .mom-text .c-orange, .prose-body .c-orange { color: var(--c-orange); }
|
||||
.prose-reading .c-orange-soft, .prose-tech .c-orange-soft, .rich .c-orange-soft, .mom-text .c-orange-soft, .prose-body .c-orange-soft { color: var(--c-orange-soft); }
|
||||
.prose-reading .c-red, .prose-tech .c-red, .rich .c-red, .mom-text .c-red, .prose-body .c-red { color: var(--c-red); }
|
||||
.prose-reading .c-red-soft, .prose-tech .c-red-soft, .rich .c-red-soft, .mom-text .c-red-soft, .prose-body .c-red-soft { color: var(--c-red-soft); }
|
||||
.prose-reading .c-pink, .prose-tech .c-pink, .rich .c-pink, .mom-text .c-pink, .prose-body .c-pink { color: var(--c-pink); }
|
||||
.prose-reading .c-pink-soft, .prose-tech .c-pink-soft, .rich .c-pink-soft, .mom-text .c-pink-soft, .prose-body .c-pink-soft { color: var(--c-pink-soft); }
|
||||
.prose-reading .c-purple, .prose-tech .c-purple, .rich .c-purple, .mom-text .c-purple, .prose-body .c-purple { color: var(--c-purple); }
|
||||
.prose-reading .c-purple-soft, .prose-tech .c-purple-soft, .rich .c-purple-soft, .mom-text .c-purple-soft, .prose-body .c-purple-soft { color: var(--c-purple-soft); }
|
||||
.prose-reading .font-serif, .prose-tech .font-serif, .rich .font-serif, .mom-text .font-serif, .prose-body .font-serif { font-family: var(--font-serif); }
|
||||
.prose-reading .font-kai, .prose-tech .font-kai, .rich .font-kai, .mom-text .font-kai, .prose-body .font-kai { font-family: var(--font-kai); }
|
||||
.prose-reading .font-sans, .prose-tech .font-sans, .rich .font-sans, .mom-text .font-sans, .prose-body .font-sans { font-family: var(--font-sans); }
|
||||
.prose-reading .font-mono, .prose-tech .font-mono, .rich .font-mono, .mom-text .font-mono, .prose-body .font-mono { font-family: var(--font-mono); font-size: 0.92em; }
|
||||
/* 系统字族回退类:不下载字体,命中访客本机字体——跨设备不保证一致(docs 已注明) */
|
||||
/* 自托管优先 + 系统兜底(2026-11-02 口径):跨设备一致性由站内 webfont 保证,
|
||||
系统字族只作最后兜底;font-fangsong 暂无 OFL 自托管包,仍为纯系统回退(docs 已注明)。 */
|
||||
.prose-reading .font-song, .prose-tech .font-song, .rich .font-song, .prose-body .font-song {
|
||||
.prose-reading .font-song, .prose-tech .font-song, .rich .font-song, .mom-text .font-song, .prose-body .font-song {
|
||||
font-family: "Noto Serif SC Variable", "Noto Serif SC", "Songti SC", SimSun, serif;
|
||||
}
|
||||
.prose-reading .font-kai-sys, .prose-tech .font-kai-sys, .rich .font-kai-sys, .prose-body .font-kai-sys {
|
||||
.prose-reading .font-kai-sys, .prose-tech .font-kai-sys, .rich .font-kai-sys, .mom-text .font-kai-sys, .prose-body .font-kai-sys {
|
||||
font-family: "LXGW WenKai Screen", "Kaiti SC", STKaiti, KaiTi, serif;
|
||||
}
|
||||
.prose-reading .font-hei-sys, .prose-tech .font-hei-sys, .rich .font-hei-sys, .prose-body .font-hei-sys {
|
||||
.prose-reading .font-hei-sys, .prose-tech .font-hei-sys, .rich .font-hei-sys, .mom-text .font-hei-sys, .prose-body .font-hei-sys {
|
||||
font-family: "Noto Sans SC Variable", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
.prose-reading .font-huiwen, .prose-tech .font-huiwen, .rich .font-huiwen, .prose-body .font-huiwen {
|
||||
.prose-reading .font-huiwen, .prose-tech .font-huiwen, .rich .font-huiwen, .mom-text .font-huiwen, .prose-body .font-huiwen {
|
||||
font-family: 'WF-Hwmc', var(--font-serif);
|
||||
}
|
||||
.prose-reading .font-kuaile, .prose-tech .font-kuaile, .rich .font-kuaile, .prose-body .font-kuaile {
|
||||
.prose-reading .font-kuaile, .prose-tech .font-kuaile, .rich .font-kuaile, .mom-text .font-kuaile, .prose-body .font-kuaile {
|
||||
font-family: 'ZCOOL KuaiLe', var(--font-serif);
|
||||
}
|
||||
/* 艺术字体专用类(层 3):仅用于标题/短句。艺术字字形大、CJK 切片体积高
|
||||
(实测 163KB/用到页),长段使用会显著抬高单页字体成本——docs 有体积警告。 */
|
||||
.prose-reading .font-art-kuaile, .prose-tech .font-art-kuaile, .rich .font-art-kuaile, .prose-body .font-art-kuaile {
|
||||
.prose-reading .font-art-kuaile, .prose-tech .font-art-kuaile, .rich .font-art-kuaile, .mom-text .font-art-kuaile, .prose-body .font-art-kuaile {
|
||||
font-family: 'ZCOOL KuaiLe', var(--font-serif);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.prose-reading .font-fangsong, .prose-tech .font-fangsong, .rich .font-fangsong, .prose-body .font-fangsong {
|
||||
.prose-reading .font-fangsong, .prose-tech .font-fangsong, .rich .font-fangsong, .mom-text .font-fangsong, .prose-body .font-fangsong {
|
||||
font-family: FangSong, STFangsong, "FangSong_GB2312", serif;
|
||||
}
|
||||
/* 文学常用:首字下沉(书评开篇)与金句块(引文式的独立强调段) */
|
||||
.prose-reading p.drop-cap::first-letter, .prose-tech p.drop-cap::first-letter, .rich p.drop-cap::first-letter, .prose-body p.drop-cap::first-letter {
|
||||
.prose-reading p.drop-cap::first-letter, .prose-tech p.drop-cap::first-letter, .rich p.drop-cap::first-letter, .mom-text p.drop-cap::first-letter, .prose-body p.drop-cap::first-letter {
|
||||
float: left;
|
||||
font-family: var(--font-serif);
|
||||
font-size: 3.1em;
|
||||
@@ -235,7 +235,7 @@ pre code { background: none; border: none; padding: 0; font-size: inherit; }
|
||||
margin-top: 0.05em;
|
||||
color: var(--paper-ink);
|
||||
}
|
||||
.prose-reading .quote-line, .prose-tech .quote-line, .rich .quote-line, .prose-body .quote-line {
|
||||
.prose-reading .quote-line, .prose-tech .quote-line, .rich .quote-line, .mom-text .quote-line, .prose-body .quote-line {
|
||||
display: block;
|
||||
font-family: var(--font-kai);
|
||||
color: var(--paper-ink);
|
||||
|
||||
Reference in new issue
Block a user