@xingwangzhe/stalux 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,372 @@
1
+ /**
2
+ * Per-route font subsetting engine.
3
+ *
4
+ * Scans each content file individually, extracts its unique character set,
5
+ * and generates a minimal WOFF2 subset per route. Also generates a "common"
6
+ * subset for UI text (navs, i18n, layout).
7
+ *
8
+ * Output layout (under public/fonts/):
9
+ * fonts/
10
+ * common.woff2 — UI text subset (ASCII, nav, i18n)
11
+ * post-{abbrlink}.woff2 — per-post subset
12
+ * about.woff2 — about page subset
13
+ * words.woff2 — combined words subset
14
+ * manifest.json — route → subset file mapping
15
+ * subset.css — global @font-face declarations
16
+ */
17
+
18
+ import { createHash } from "node:crypto";
19
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
20
+ import { resolve, basename, extname, join } from "node:path";
21
+
22
+ import type { AstroIntegrationLogger } from "astro";
23
+ // subset-font 是 WASM 模块,需要静态导入(Vite module runner 在构建钩子中不兼容动态 import)
24
+ import subsetFontFn from "subset-font";
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Configuration
28
+ // ---------------------------------------------------------------------------
29
+
30
+ /** Content root directory (relative to project root) */
31
+ const CONTENT_ROOT = "stalux";
32
+
33
+ /** Full font input path */
34
+ const FONT_INPUT = "src/assets/fonts/LXGWWenKai-Regular.ttf";
35
+
36
+ /** Output directory for generated subsets */
37
+ const FONT_OUT_DIR = "public/fonts";
38
+
39
+ /** Characters always included in common subset */
40
+ const ALWAYS_INCLUDE_CHARS = new Set(
41
+ // ASCII printable
42
+ [...Array(0x7e - 0x20 + 1)]
43
+ .map((_, i) => String.fromCodePoint(0x20 + i))
44
+ .join("")
45
+ // Basic CJK punctuation
46
+ .concat(
47
+ "\u3000\u3001\u3002\u3008\u3009\u300a\u300b\u300c\u300d\u3010\u3011" +
48
+ "\uff01\uff08\uff09\uff0c\uff0e\uff1a\uff1b\uff1f\uff5e\u2013\u2014" +
49
+ "\u2018\u2019\u201c\u201d\u2026\uff09\uff08\u00b7",
50
+ ),
51
+ );
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Types
55
+ // ---------------------------------------------------------------------------
56
+
57
+ interface RouteCharSet {
58
+ route: string;
59
+ chars: string;
60
+ hash: string;
61
+ }
62
+
63
+ interface FontManifest {
64
+ common: string; // filename of common subset
65
+ routes: Record<string, string>; // route → subset filename
66
+ /** Per-post mapping: abbrlink → subset filename */
67
+ posts: Record<string, string>;
68
+ }
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Character extraction
72
+ // ---------------------------------------------------------------------------
73
+
74
+ function extractChars(text: string): string {
75
+ const set = new Set<string>();
76
+ for (const ch of text) set.add(ch);
77
+ return [...set].sort().join("");
78
+ }
79
+
80
+ function hashChars(chars: string): string {
81
+ return createHash("md5").update(chars).digest("hex").slice(0, 12);
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Content scanning
86
+ // ---------------------------------------------------------------------------
87
+
88
+ interface ContentFile {
89
+ route: string;
90
+ filePath: string;
91
+ abbrlink?: string;
92
+ }
93
+
94
+ function scanContent(projectRoot: string): ContentFile[] {
95
+ const files: ContentFile[] = [];
96
+ const contentRoot = resolve(projectRoot, CONTENT_ROOT);
97
+
98
+ // Posts
99
+ const postsDir = join(contentRoot, "posts");
100
+ if (existsSync(postsDir)) {
101
+ for (const f of readdirSync(postsDir)) {
102
+ if (!f.endsWith(".md") && !f.endsWith(".mdx")) continue;
103
+ if (f.startsWith("_")) continue; // skip template files
104
+ const content = readFileSync(join(postsDir, f), "utf-8");
105
+ // Extract abbrlink from frontmatter
106
+ const abbrlinkMatch = content.match(/^abbrlink:\s*(.+)$/m);
107
+ const abbrlink = abbrlinkMatch?.[1]?.trim() ?? basename(f, extname(f));
108
+ files.push({
109
+ route: `posts/${abbrlink}`,
110
+ filePath: join(postsDir, f),
111
+ abbrlink,
112
+ });
113
+ }
114
+ }
115
+
116
+ // About
117
+ const aboutDir = join(contentRoot, "about");
118
+ if (existsSync(aboutDir)) {
119
+ for (const f of readdirSync(aboutDir)) {
120
+ if (!f.endsWith(".md") && !f.endsWith(".mdx")) continue;
121
+ if (f.startsWith("_")) continue;
122
+ files.push({
123
+ route: "about",
124
+ filePath: join(aboutDir, f),
125
+ });
126
+ }
127
+ }
128
+
129
+ // Words - aggregate all words files' characters into one
130
+ const wordsDir = join(contentRoot, "words");
131
+ if (existsSync(wordsDir)) {
132
+ const allWordsContent: string[] = [];
133
+ for (const f of readdirSync(wordsDir)) {
134
+ if (!f.endsWith(".md")) continue;
135
+ if (f.startsWith("_")) continue;
136
+ try {
137
+ allWordsContent.push(readFileSync(join(wordsDir, f), "utf-8"));
138
+ } catch {
139
+ /* skip */
140
+ }
141
+ }
142
+ if (allWordsContent.length > 0) {
143
+ files.push({
144
+ route: "words",
145
+ filePath: join(wordsDir, "words-combined.md"),
146
+ // We'll handle this virtual file specially in char extraction
147
+ });
148
+ // Store combined content in a map for later extraction
149
+ wordCharsCache.set("words", extractChars(allWordsContent.join("\n")));
150
+ }
151
+ }
152
+
153
+ return files;
154
+ }
155
+
156
+ // Cache for virtual route char sets
157
+ const wordCharsCache = new Map<string, string>();
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // Common subset: extract chars from UI source files
161
+ // ---------------------------------------------------------------------------
162
+
163
+ function collectCommonChars(projectRoot: string): string {
164
+ const chars = new Set(ALWAYS_INCLUDE_CHARS);
165
+
166
+ const scanPatterns = [
167
+ "src/i18n/*.json",
168
+ "src/components/stalux/**/*.astro",
169
+ "src/layouts/**/*.astro",
170
+ "src/pages/**/*.astro",
171
+ "src/pages/**/*.ts",
172
+ ];
173
+
174
+ // Since we can't use glob here easily, scan specific known files
175
+ const srcDir = resolve(projectRoot, "src");
176
+ const scanDirs = ["i18n", "components/stalux", "layouts", "pages"];
177
+
178
+ function scanDir(dir: string) {
179
+ if (!existsSync(dir)) return;
180
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
181
+ const fullPath = join(dir, entry.name);
182
+ if (entry.isDirectory()) {
183
+ scanDir(fullPath);
184
+ } else if (
185
+ entry.name.endsWith(".astro") ||
186
+ entry.name.endsWith(".ts") ||
187
+ entry.name.endsWith(".json")
188
+ ) {
189
+ try {
190
+ const content = readFileSync(fullPath, "utf-8");
191
+ // Only extract string literals and text content (skip code)
192
+ for (const ch of content) chars.add(ch);
193
+ } catch {
194
+ /* skip */
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ scanDir(srcDir);
201
+
202
+ return [...chars].sort().join("");
203
+ }
204
+
205
+ // ---------------------------------------------------------------------------
206
+ // Deduplication: merge files with identical char sets
207
+ // ---------------------------------------------------------------------------
208
+
209
+ function deduplicate(routes: RouteCharSet[]): Map<string, string[]> {
210
+ // hash → [route1, route2, ...]
211
+ const groups = new Map<string, string[]>();
212
+ for (const r of routes) {
213
+ const list = groups.get(r.hash) ?? [];
214
+ list.push(r.route);
215
+ groups.set(r.hash, list);
216
+ }
217
+ return groups;
218
+ }
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // Main entry
222
+ // ---------------------------------------------------------------------------
223
+
224
+ /**
225
+ * Run font subsetting during build.
226
+ * Called from astro:build:start hook.
227
+ */
228
+ export async function runFontSubsetting(
229
+ projectRoot: string,
230
+ logger: AstroIntegrationLogger,
231
+ ): Promise<void> {
232
+ logger.info("Font subsetting: scanning content...");
233
+
234
+ // 1. Locate font file
235
+ const fontPath = resolve(projectRoot, FONT_INPUT);
236
+ if (!existsSync(fontPath)) {
237
+ logger.warn(`Font not found at ${fontPath}, skipping subsetting`);
238
+ return;
239
+ }
240
+ const fontBuffer = readFileSync(fontPath);
241
+
242
+ // 2. Scan content files
243
+ const contentFiles = scanContent(projectRoot);
244
+ logger.info(`Font subsetting: found ${contentFiles.length} content files`);
245
+
246
+ // 3. Collect common chars (UI, nav, i18n)
247
+ const commonChars = collectCommonChars(projectRoot);
248
+ const commonHash = hashChars(commonChars);
249
+
250
+ // 4. Extract per-route char sets
251
+ const routeSets: RouteCharSet[] = [];
252
+ for (const file of contentFiles) {
253
+ let content: string;
254
+ if (file.route === "words" && wordCharsCache.has("words")) {
255
+ content = wordCharsCache.get("words")!;
256
+ } else {
257
+ content = readFileSync(file.filePath, "utf-8");
258
+ }
259
+ const chars = extractChars(content);
260
+ routeSets.push({
261
+ route: file.route,
262
+ chars,
263
+ hash: hashChars(chars),
264
+ });
265
+ }
266
+
267
+ // 5. Deduplicate and generate subsets
268
+ const outDir = resolve(projectRoot, FONT_OUT_DIR);
269
+ mkdirSync(outDir, { recursive: true });
270
+
271
+ // Dynamic import subset-font (ESM only)
272
+ let subsetFont = subsetFontFn;
273
+
274
+ // Generate common subset
275
+ const commonFilename = `common-${commonHash}.woff2`;
276
+ const commonOutPath = join(outDir, commonFilename);
277
+ if (!existsSync(commonOutPath)) {
278
+ const data = await subsetFont(fontBuffer, commonChars, { targetFormat: "woff2" });
279
+ writeFileSync(commonOutPath, data);
280
+ logger.info(` common subset: ${(data.length / 1024).toFixed(1)} KB → ${commonFilename}`);
281
+ }
282
+
283
+ // Generate common CSS
284
+ const commonCSS = `/* Auto-generated common font subset */
285
+ @font-face {
286
+ font-family: "LXGW WenKai Subset";
287
+ src: url("/fonts/${commonFilename}") format("woff2");
288
+ font-display: swap;
289
+ }
290
+ `;
291
+ writeFileSync(join(outDir, "common.css"), commonCSS);
292
+
293
+ // Generate per-route subsets (deduplicated) and their CSS
294
+ const groups = deduplicate(routeSets);
295
+ const routeMap: Record<string, string> = {};
296
+ const postMap: Record<string, string> = {};
297
+
298
+ let subsetCount = 0;
299
+ // Track which subsets we've already generated CSS for
300
+ const cssGenerated = new Set<string>();
301
+
302
+ for (const [hash, routes] of groups) {
303
+ const filename = `subset-${hash}.woff2`;
304
+ const outPath = join(outDir, filename);
305
+ if (!existsSync(outPath)) {
306
+ // Find the route chars for this hash
307
+ const route = routeSets.find((r) => r.hash === hash);
308
+ const chars = route?.chars ?? "";
309
+ // Only subset characters NOT in common (already covered by common.css)
310
+ const uniqueChars = chars
311
+ .split("")
312
+ .filter((ch) => !commonChars.includes(ch))
313
+ .join("");
314
+ if (uniqueChars.length > 0) {
315
+ const data = await subsetFont(fontBuffer, uniqueChars, { targetFormat: "woff2" });
316
+ writeFileSync(outPath, data);
317
+ subsetCount++;
318
+ logger.info(
319
+ ` subset ${subsetCount}: ${(data.length / 1024).toFixed(1)} KB → ${filename}`,
320
+ );
321
+ }
322
+ }
323
+
324
+ // Generate per-route CSS (one per unique hash, references all routes with this hash)
325
+ if (!cssGenerated.has(hash)) {
326
+ cssGenerated.add(hash);
327
+ for (const route of routes) {
328
+ const cssContent = `/* Auto-generated font subset for route: ${route} */
329
+ @font-face {
330
+ font-family: "LXGW WenKai Subset";
331
+ src: url("/fonts/${filename}") format("woff2");
332
+ font-display: swap;
333
+ }
334
+ `;
335
+ // Use first route's name as the CSS filename (they share the same subset)
336
+ const cssName = routes.length === 1 ? route.replace("/", "-") : `group-${hash}`;
337
+ const cssFilename = `subset-${cssName}.css`;
338
+ writeFileSync(join(outDir, cssFilename), cssContent);
339
+ routeMap[route] = cssFilename;
340
+
341
+ if (route.startsWith("posts/")) {
342
+ const abbrlink = route.replace("posts/", "");
343
+ postMap[abbrlink] = cssFilename;
344
+ }
345
+ }
346
+ } else {
347
+ // CSS already generated, just map
348
+ for (const route of routes) {
349
+ const cssFilename =
350
+ routes.length === 1
351
+ ? `subset-${route.replace("/", "-")}.css`
352
+ : `subset-group-${hash}.css`;
353
+ routeMap[route] = cssFilename;
354
+ if (route.startsWith("posts/")) {
355
+ postMap[route.replace("posts/", "")] = cssFilename;
356
+ }
357
+ }
358
+ }
359
+ }
360
+
361
+ // 6. Generate manifest
362
+ const manifest: FontManifest = {
363
+ common: commonFilename,
364
+ routes: routeMap,
365
+ posts: postMap,
366
+ };
367
+ writeFileSync(join(outDir, "manifest.json"), JSON.stringify(manifest, null, 2));
368
+
369
+ logger.info(
370
+ `Font subsetting done: ${subsetCount} route subsets + common (${(fontBuffer.length / 1024 / 1024).toFixed(1)} MB → variable)`,
371
+ );
372
+ }
@@ -14,68 +14,68 @@ import type { ComponentOverrideMap } from "../config";
14
14
  // ---------------------------------------------------------------------------
15
15
 
16
16
  export const STALUX_COMPONENTS = {
17
- // Layout
18
- Head: "layouts/head.astro",
19
- Footer: "layouts/footer.astro",
20
- Navs: "layouts/navs.astro",
21
- ListPage: "layouts/listPage.astro",
17
+ // Layout
18
+ Head: "layouts/head.astro",
19
+ Footer: "layouts/footer.astro",
20
+ Navs: "layouts/navs.astro",
21
+ ListPage: "layouts/listPage.astro",
22
22
 
23
- // Common
24
- Author: "common/author.astro",
25
- Date: "common/date.astro",
26
- Search: "common/search.astro",
27
- TextType: "common/textType.astro",
28
- VerCount: "common/verCount.astro",
23
+ // Common
24
+ Author: "common/author.astro",
25
+ Date: "common/date.astro",
26
+ Search: "common/search.astro",
27
+ TextType: "common/textType.astro",
28
+ VerCount: "common/verCount.astro",
29
29
 
30
- // Home
31
- SocialMedia: "links/socialMedia.astro",
32
- ToArchive: "archives/toArchive.astro",
30
+ // Home
31
+ SocialMedia: "links/socialMedia.astro",
32
+ ToArchive: "archives/toArchive.astro",
33
33
 
34
- // Posts
35
- PostLeft: "posts/postLeft.astro",
36
- PostContent: "posts/postContent.astro",
37
- PostRight: "posts/postRight.astro",
38
- PostNavigation: "posts/postNavigation.astro",
39
- PostCC: "posts/postCC.astro",
40
- WalineComment: "posts/WalineComment.astro",
41
- PostToc: "posts/postToc.astro",
42
- PostAuthor: "posts/postAuthor.astro",
43
- PostCard: "posts/postCard.astro",
44
- PostList: "posts/postList.astro",
45
- PostTaxonomy: "posts/PostTaxonomy.astro",
46
- PhotoSwipeStyle: "posts/PhotoSwipeStyle.astro",
47
- RandomPost: "posts/randomPost.astro",
34
+ // Posts
35
+ PostLeft: "posts/postLeft.astro",
36
+ PostContent: "posts/postContent.astro",
37
+ PostRight: "posts/postRight.astro",
38
+ PostNavigation: "posts/postNavigation.astro",
39
+ PostCC: "posts/postCC.astro",
40
+ WalineComment: "posts/WalineComment.astro",
41
+ PostToc: "posts/postToc.astro",
42
+ PostAuthor: "posts/postAuthor.astro",
43
+ PostCard: "posts/postCard.astro",
44
+ PostList: "posts/postList.astro",
45
+ PostTaxonomy: "posts/PostTaxonomy.astro",
46
+ PhotoSwipeStyle: "posts/PhotoSwipeStyle.astro",
47
+ RandomPost: "posts/randomPost.astro",
48
48
 
49
- // Archives
50
- ArchivesList: "archives/archivesList.astro",
49
+ // Archives
50
+ ArchivesList: "archives/archivesList.astro",
51
51
 
52
- // Categories
53
- CategoriesCard: "categories/categoriesCard.astro",
54
- CategoriesList: "categories/categoriesList.astro",
52
+ // Categories
53
+ CategoriesCard: "categories/categoriesCard.astro",
54
+ CategoriesList: "categories/categoriesList.astro",
55
55
 
56
- // Tags
57
- TagHeader: "tags/tagHeader.astro",
58
- TagsCloud: "tags/tagsCloud.astro",
56
+ // Tags
57
+ TagHeader: "tags/tagHeader.astro",
58
+ TagsCloud: "tags/tagsCloud.astro",
59
59
 
60
- // Words
61
- WordCard: "words/wordCard.astro",
62
- WordList: "words/wordList.astro",
60
+ // Words
61
+ WordCard: "words/wordCard.astro",
62
+ WordList: "words/wordList.astro",
63
63
 
64
- // Links
65
- LinkCard: "links/linkCard.astro",
66
- LinkList: "links/linkList.astro",
64
+ // Links
65
+ LinkCard: "links/linkCard.astro",
66
+ LinkList: "links/linkList.astro",
67
67
 
68
- // Footer
69
- FooterBadges: "footer/FooterBadges.astro",
70
- FooterBeian: "footer/FooterBeian.astro",
71
- FooterCopyright: "footer/FooterCopyright.astro",
72
- FooterStats: "footer/FooterStats.astro",
73
- FooterThemeInfo: "footer/FooterThemeInfo.astro",
68
+ // Footer
69
+ FooterBadges: "footer/FooterBadges.astro",
70
+ FooterBeian: "footer/FooterBeian.astro",
71
+ FooterCopyright: "footer/FooterCopyright.astro",
72
+ FooterStats: "footer/FooterStats.astro",
73
+ FooterThemeInfo: "footer/FooterThemeInfo.astro",
74
74
 
75
- // Analytics
76
- Clarity: "analytics/clarity.astro",
77
- Google: "analytics/google.astro",
78
- Umami: "analytics/umami.astro",
75
+ // Analytics
76
+ Clarity: "analytics/clarity.astro",
77
+ Google: "analytics/google.astro",
78
+ Umami: "analytics/umami.astro",
79
79
  } as const;
80
80
 
81
81
  /** 组件键名联合类型 */
@@ -90,10 +90,10 @@ export type StaluxComponentKey = keyof typeof STALUX_COMPONENTS;
90
90
  * @returns 组件的绝对路径
91
91
  */
92
92
  export function resolveComponentPath(
93
- key: StaluxComponentKey,
94
- overrides: ComponentOverrideMap = {},
95
- componentsDir: string,
93
+ key: StaluxComponentKey,
94
+ overrides: ComponentOverrideMap = {},
95
+ componentsDir: string,
96
96
  ): string {
97
- if (overrides[key]) return overrides[key]!;
98
- return componentsDir + "/stalux/" + STALUX_COMPONENTS[key];
97
+ if (overrides[key]) return overrides[key]!;
98
+ return componentsDir + "/stalux/" + STALUX_COMPONENTS[key];
99
99
  }
@@ -7,34 +7,35 @@
7
7
 
8
8
  import path from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
+
10
11
  import type { AstroIntegrationLogger } from "astro";
11
12
 
12
13
  export async function pagefind(dir: URL, logger: AstroIntegrationLogger): Promise<void> {
13
- const outDir = fileURLToPath(dir);
14
- logger.info("Running Pagefind indexer...");
14
+ const outDir = fileURLToPath(dir);
15
+ logger.info("Running Pagefind indexer...");
15
16
 
16
- try {
17
- const { createIndex } = await import("pagefind");
18
- const { index, errors } = await createIndex();
17
+ try {
18
+ const { createIndex } = await import("pagefind");
19
+ const { index, errors } = await createIndex();
19
20
 
20
- if (!index) {
21
- logger.error("Pagefind failed to create index");
22
- errors?.forEach((e) => logger.error(e));
23
- return;
24
- }
21
+ if (!index) {
22
+ logger.error("Pagefind failed to create index");
23
+ errors?.forEach((e) => logger.error(e));
24
+ return;
25
+ }
25
26
 
26
- const { page_count } = await index.addDirectory({ path: outDir });
27
- if (page_count === 0) {
28
- logger.warn("Pagefind: no pages indexed");
29
- return;
30
- }
27
+ const { page_count } = await index.addDirectory({ path: outDir });
28
+ if (page_count === 0) {
29
+ logger.warn("Pagefind: no pages indexed");
30
+ return;
31
+ }
31
32
 
32
- const { outputPath } = await index.writeFiles({
33
- outputPath: path.join(outDir, "pagefind"),
34
- });
33
+ const { outputPath } = await index.writeFiles({
34
+ outputPath: path.join(outDir, "pagefind"),
35
+ });
35
36
 
36
- logger.info(`Pagefind indexed ${page_count} pages → ${outputPath}`);
37
- } catch (error) {
38
- logger.error(`Pagefind indexing failed: ${error}`);
39
- }
37
+ logger.info(`Pagefind indexed ${page_count} pages → ${outputPath}`);
38
+ } catch (error) {
39
+ logger.error(`Pagefind indexing failed: ${error}`);
40
+ }
40
41
  }
@@ -18,6 +18,8 @@ interface Props {
18
18
  categories?: string[];
19
19
  author?: string;
20
20
  noindex?: boolean;
21
+ /** Font subset ID for per-route font optimization */
22
+ fontSubsetId?: string;
21
23
  }
22
24
 
23
25
  const configEntries = await getCollection("config");
@@ -26,6 +28,11 @@ const authorConfig = getAuthorData(configEntries);
26
28
  const promoteConfig = getPromoteData(configEntries);
27
29
  const htmlLang = siteConfig.lang || "zh-CN";
28
30
 
31
+ // Resolve font subset CSS URL
32
+ const fontSubsetUrl = props.fontSubsetId
33
+ ? `/fonts/subset-${props.fontSubsetId}.css`
34
+ : undefined;
35
+
29
36
  const bgModules = import.meta.glob<{ default: { src: string } }>(
30
37
  "../assets/background/pattern-*.min.svg",
31
38
  { eager: true },
@@ -61,6 +68,9 @@ const llmPromote = llmPromoteRaw
61
68
  author={props.author}
62
69
  noindex={props.noindex}
63
70
  />
71
+ <!-- Per-route font subset (generated at build time) -->
72
+ <link rel="stylesheet" href="/fonts/common.css" />
73
+ {fontSubsetUrl && <link rel="stylesheet" href={fontSubsetUrl} />}
64
74
  <script define:vars={{ bgUrls, randomIndex }}>
65
75
  window.__STALUX_BG_URLS__ = bgUrls;
66
76
  window.__STALUX_BG_INDEX__ = randomIndex;
@@ -13,6 +13,7 @@ const { t } = createTranslator(siteConfig.lang || "zh-CN");
13
13
  <Stalux
14
14
  title={t("page404.title") + " | " + siteConfig.title}
15
15
  description={t("page404.description")}
16
+ fontSubsetId="common"
16
17
  >
17
18
  <div style="height: 3em;"></div>
18
19
  <Navs />
@@ -27,6 +27,7 @@ const author = authorConfig.name;
27
27
  <Stalux
28
28
  title={aboutPage.data.title + " | " + siteConfig.title}
29
29
  description={aboutPage.data.description}
30
+ fontSubsetId="about"
30
31
  >
31
32
  <div style="height: 3em;"></div>
32
33
  <Navs />
@@ -12,7 +12,7 @@ const { t } = createTranslator(siteConfig.lang || "zh-CN");
12
12
  const posts = await getCollection("posts", ({ data }) => !data.draft);
13
13
  ---
14
14
 
15
- <Stalux title={t("archives.title") + " | " + siteConfig.title} description={t("archives.description", { title: siteConfig.title })}>
15
+ <Stalux title={t("archives.title") + " | " + siteConfig.title} description={t("archives.description", { title: siteConfig.title })} fontSubsetId="archives">
16
16
  <div style="height: 3em;"></div>
17
17
  <Navs />
18
18
  <h1 class={styles.h1}>{t("archives.heading")}</h1>
@@ -27,6 +27,7 @@ const props = Astro.props as Props;
27
27
  <Stalux
28
28
  title={t("taxonomy.category") + ": " + props.category + " | " + siteConfig.title}
29
29
  description={t("taxonomy.browseCategory", { title: siteConfig.title, category: props.category, count: props.count })}
30
+ fontSubsetId="categories"
30
31
  >
31
32
  <div style="height: 3em;"></div>
32
33
  <Navs />
@@ -14,6 +14,7 @@ const { t } = createTranslator(siteConfig.lang || "zh-CN");
14
14
  <Stalux
15
15
  title={t("categories.title") + " | " + siteConfig.title}
16
16
  description={t("categories.description", { title: siteConfig.title })}
17
+ fontSubsetId="categories"
17
18
  >
18
19
  <div style="height: 3em;"></div>
19
20
  <Navs />
@@ -15,7 +15,7 @@ import ToArchive from "@components/stalux/archives/toArchive.astro";
15
15
  import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, getCommentData, getFooterData, getPromoteData, getTypetextsData, getMediaLinksData } from "@utils/config-utils";
16
16
  ---
17
17
 
18
- <Stalux title={siteTitle} description={siteDescription}>
18
+ <Stalux title={siteTitle} description={siteDescription} fontSubsetId="home">
19
19
  <div class={styles.homeBody}>
20
20
  <h1 class={styles.homeTitle}>{siteTitle}</h1>
21
21
  <div class={styles.homeNavs}>
@@ -13,11 +13,12 @@ const linksTitle = linksConfig.title;
13
13
  const linksDescription = linksConfig.description;
14
14
  ---
15
15
 
16
- <ListPage
17
- title={siteTitle}
18
- description={siteDescription}
19
- heading={linksTitle}
20
- subheading={linksDescription}
21
- >
16
+ <ListPage
17
+ title={siteTitle}
18
+ description={siteDescription}
19
+ heading={linksTitle}
20
+ subheading={linksDescription}
21
+ fontSubsetId="links"
22
+ >
22
23
  <LinkList />
23
24
  </ListPage>