diff --git a/docs/development.md b/docs/development.md
index e1cccf8..af16bee 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -90,7 +90,7 @@ Default font resources are checked in; ordinary builds require no download or co
python3 scripts/prepare-maple-font.py /path/to/MapleMonoNormalNL-NF-CN.zip
```
-The script generates WOFF2 files and `maple-font.css` while retaining the upstream license. Code blocks use highlight.js with fixed Atom One Dark; theme changes must not reintroduce previous highlighters or color schemes.
+The script generates WOFF2 files and `maple-font.css` while retaining the upstream license; `--css-only` regenerates declarations without rebuilding font binaries. Fonts use swap rendering, so downloads never hide the page. After first paint, regular Maple chunks (about 6.72 MB) load in batches of two; other weights load on demand. PWA installation skips font files and caches public build fonts after use. Code blocks use highlight.js with fixed Atom One Dark; theme changes must not reintroduce previous highlighters or color schemes.
Plugin SDK types are in [common/plugin-api/index.d.ts](../web/src/common/plugin-api/index.d.ts); loading and lifecycle code lives in `common/plugins/`. Plugins run only on the frontend, and installers must trust their code. Namespaces, disabling, and resource versioning do not constitute a permission sandbox. Editor-extension failures preserve document contents and disable the relevant extensions on the current page.
diff --git a/scripts/prepare-maple-font.py b/scripts/prepare-maple-font.py
index 380bd57..6f4d997 100644
--- a/scripts/prepare-maple-font.py
+++ b/scripts/prepare-maple-font.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
"""Rebuild local fonts from the specified v7.9 ZIP: python3 scripts/prepare-maple-font.py /path/to/MapleMonoNormalNL-NF-CN.zip.
+Use --css-only to regenerate declarations from checked-in chunks.
Only font regeneration requires fonttools[woff]; application builds use the checked-in WOFF2 files.
Source: https://github.com/subframe7536/maple-font/releases/tag/v7.9
"""
@@ -8,31 +9,37 @@ from pathlib import Path
import sys
import zipfile
-from fontTools import subset
-from fontTools.ttLib import TTFont
-
root = Path(__file__).resolve().parents[1] / 'web/src/common/appearance'
output = root / 'fonts/maple'
output.mkdir(exist_ok=True)
family = 'Maple Mono Normal NL NF CN'
+if sys.argv[1:] != ['--css-only']:
+ from fontTools import subset
+ from fontTools.ttLib import TTFont
+ with zipfile.ZipFile(sys.argv[1]) as archive:
+ (output / 'LICENSE.txt').write_bytes(archive.read('LICENSE.txt'))
+ for variant, weight, style in [('Regular', 400, 'normal'), ('Bold', 700, 'normal'), ('Italic', 400, 'italic'), ('BoldItalic', 700, 'italic')]:
+ data = archive.read('MapleMonoNormalNL-NF-CN-' + variant + '.ttf')
+ source = TTFont(io.BytesIO(data))
+ points = sorted(source.getBestCmap())
+ # Split by Unicode range so Chinese and Nerd Font glyphs need not download alongside Latin text.
+ for block in sorted({cp // 4096 for cp in points}):
+ unicodes = [cp for cp in points if cp // 4096 == block]
+ font = TTFont(io.BytesIO(data), recalcTimestamp=False)
+ options = subset.Options()
+ options.flavor = 'woff2'
+ subsetter = subset.Subsetter(options=options)
+ subsetter.populate(unicodes=unicodes)
+ subsetter.subset(font)
+ font.flavor = 'woff2'
+ name = f'{variant.lower()}-{block:02x}.woff2'
+ font.save(output / name)
css = ['/* Maple Mono v7.9; generated by scripts/prepare-maple-font.py. See fonts/maple/LICENSE.txt for licensing. */']
-with zipfile.ZipFile(sys.argv[1]) as archive:
- (output / 'LICENSE.txt').write_bytes(archive.read('LICENSE.txt'))
- for variant, weight, style in [('Regular', 400, 'normal'), ('Bold', 700, 'normal'), ('Italic', 400, 'italic'), ('BoldItalic', 700, 'italic')]:
- data = archive.read('MapleMonoNormalNL-NF-CN-' + variant + '.ttf')
- source = TTFont(io.BytesIO(data))
- points = sorted(source.getBestCmap())
- # Split by Unicode range so Chinese and Nerd Font glyphs need not download alongside Latin text.
- for block in sorted({cp // 4096 for cp in points}):
- unicodes = [cp for cp in points if cp // 4096 == block]
- font = TTFont(io.BytesIO(data), recalcTimestamp=False)
- options = subset.Options()
- options.flavor = 'woff2'
- subsetter = subset.Subsetter(options=options)
- subsetter.populate(unicodes=unicodes)
- subsetter.subset(font)
- font.flavor = 'woff2'
- name = f'{variant.lower()}-{block:02x}.woff2'
- font.save(output / name)
- css.append(f"@font-face {{ font-family: '{family}'; font-style: {style}; font-weight: {weight}; font-display: block; src: url('./fonts/maple/{name}') format('woff2'); unicode-range: U+{block * 4096:X}-{(block + 1) * 4096 - 1:X}; }}")
+for variant, weight, style in [('regular', 400, 'normal'), ('bold', 700, 'normal'), ('italic', 400, 'italic'), ('bolditalic', 700, 'italic')]:
+ files = sorted(output.glob(f'{variant}-*.woff2'))
+ if not files:
+ raise ValueError(f'Missing checked-in font chunks: {variant}')
+ for file in files:
+ block = int(file.stem.split('-')[1], 16)
+ css.append(f"@font-face {{ font-family: '{family}'; font-style: {style}; font-weight: {weight}; font-display: swap; src: url('./fonts/maple/{file.name}') format('woff2'); unicode-range: U+{block * 4096:X}-{(block + 1) * 4096 - 1:X}; }}")
(root / 'maple-font.css').write_text('\n'.join(css) + '\n')
diff --git a/web/demo.html b/web/demo.html
index 6d91a8d..4bf9043 100644
--- a/web/demo.html
+++ b/web/demo.html
@@ -1,5 +1,5 @@
-
+
diff --git a/web/index.html b/web/index.html
index 03cf99c..b3f8ead 100644
--- a/web/index.html
+++ b/web/index.html
@@ -1,5 +1,5 @@
-
+
diff --git a/web/src/common/appearance/base.css b/web/src/common/appearance/base.css
index 1ccf691..39611f2 100644
--- a/web/src/common/appearance/base.css
+++ b/web/src/common/appearance/base.css
@@ -628,20 +628,18 @@ textarea {
}
}
-/* opacity preserves layout and font requests; hide fallback fonts while waiting, including menus teleported to body. */
-:root:is([data-rm-fonts='loading'], [data-rm-fonts='error']) body > :not(.rm-font-error) {
- opacity: 0;
- pointer-events: none;
-}
.rm-font-error {
position: fixed;
- inset: 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 24px;
+ z-index: 10000;
+ right: 16px;
+ bottom: 16px;
+ max-width: min(360px, calc(100vw - 64px));
+ padding: 16px;
+ border: 1px solid var(--rm-divider);
+ border-radius: 8px;
+ background: var(--rm-bg);
color: var(--rm-text);
+ box-shadow: var(--rm-shadow);
font:
16px/1.5 system-ui,
sans-serif;
diff --git a/web/src/common/appearance/font-loading.test.ts b/web/src/common/appearance/font-loading.test.ts
index e50e816..881348b 100644
--- a/web/src/common/appearance/font-loading.test.ts
+++ b/web/src/common/appearance/font-loading.test.ts
@@ -2,96 +2,66 @@
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
import { watchFontLoading } from './font-loading'
+function face(overrides = {}) {
+ return { family: 'Maple Mono Normal NL NF CN', weight: '400', style: 'normal', status: 'unloaded', load: vi.fn().mockResolvedValue(undefined), ...overrides }
+}
class Fonts extends EventTarget {
- status = 'loading'
- faces: { status: string }[] = []
- resolve!: () => void
- ready = this.pending()
- pending() {
- return new Promise((resolve) => {
- this.resolve = resolve
- })
- }
-
+ faces = [face()];
[Symbol.iterator]() {
return this.faces[Symbol.iterator]()
}
-
- finish() {
- this.status = 'loaded'; this.resolve()
- }
-
- load() {
- this.status = 'loading'; this.ready = this.pending(); this.dispatchEvent(new Event('loading'))
- }
}
let fonts: Fonts
let frame: FrameRequestCallback
const descriptor = Object.getOwnPropertyDescriptor(document, 'fonts')
beforeEach(() => {
+ vi.useFakeTimers()
document.body.innerHTML = '正文
'
fonts = new Fonts()
Object.defineProperty(document, 'fonts', { configurable: true, value: fonts })
vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => {
frame = callback; return 1
})
- watchFontLoading()
})
afterEach(() => {
- vi.unstubAllGlobals()
- delete document.documentElement.dataset.rmFonts
+ vi.useRealTimers(); vi.unstubAllGlobals()
document.body.innerHTML = ''
if (descriptor) Object.defineProperty(document, 'fonts', descriptor)
else Reflect.deleteProperty(document, 'fonts')
})
-
-it('挂载后的字体就绪前保持隐藏,等待结束才显示', async () => {
- expect(document.documentElement.dataset.rmFonts).toBe('loading')
+it('首屏与后续字体加载均不隐藏或禁用应用,预下载不阻塞首屏', async () => {
+ watchFontLoading()
+ expect(fonts.faces[0]!.load).not.toHaveBeenCalled()
+ fonts.dispatchEvent(new Event('loading'))
+ expect(document.documentElement.dataset.rmFonts).toBeUndefined()
+ expect(getComputedStyle(document.getElementById('app')!).pointerEvents).not.toBe('none')
frame(0)
- await Promise.resolve()
- expect(document.documentElement.dataset.rmFonts).toBe('loading')
- fonts.finish()
- await Promise.resolve()
- expect(document.documentElement.dataset.rmFonts).toBe('ready')
+ expect(fonts.faces[0]!.load).not.toHaveBeenCalled()
+ await vi.advanceTimersByTimeAsync(0)
+ expect(fonts.faces[0]!.load).toHaveBeenCalledOnce()
})
-
-it('字体已在缓存中时直接显示,不依赖再次触发 loading 事件', async () => {
- fonts.finish()
- frame(0)
- await Promise.resolve()
- expect(document.documentElement.dataset.rmFonts).toBe('ready')
-})
-
-it('切换笔记或字形需要新字体时重新等待,不用旧一轮完成信号提前显示', async () => {
- frame(0)
- fonts.finish()
- fonts.load()
- await Promise.resolve()
- expect(document.documentElement.dataset.rmFonts).toBe('loading')
- fonts.finish()
- await Promise.resolve()
- expect(document.documentElement.dataset.rmFonts).toBe('ready')
-})
-
-it('字体失败显示可刷新提示,之后 ready 完成和其他字体加载不能显示备用字体', async () => {
- frame(0)
- fonts.dispatchEvent(new Event('loadingerror'))
- fonts.dispatchEvent(new Event('loadingerror'))
- fonts.finish()
- await Promise.resolve()
- expect(document.documentElement.dataset.rmFonts).toBe('error')
+it('只预下载常规Maple,每批两个,单片失败仍继续且不重复启动', async () => {
+ let finish!: () => void
+ fonts.faces = [face({ family: '\'Maple Mono Normal NL NF CN\'', load: vi.fn(() => new Promise((resolve) => {
+ finish = resolve
+ })) }), face({ load: vi.fn().mockRejectedValue(new Error('offline')) }), face(), face({ weight: '700' }), face({ style: 'italic' }), face({ family: 'Other font' })]
+ watchFontLoading(); frame(0)
+ await vi.advanceTimersByTimeAsync(0)
+ expect(fonts.faces[0]!.load).toHaveBeenCalledOnce()
+ expect(fonts.faces[1]!.load).toHaveBeenCalledOnce()
+ expect(fonts.faces[2]!.load).not.toHaveBeenCalled()
+ finish(); await vi.advanceTimersByTimeAsync(0)
+ expect(fonts.faces[2]!.load).toHaveBeenCalledOnce()
+ for (const font of fonts.faces.slice(3)) expect(font.load).not.toHaveBeenCalled()
+ watchFontLoading(); await vi.advanceTimersByTimeAsync(0)
+ expect(fonts.faces[0]!.load).toHaveBeenCalledOnce()
expect(document.querySelectorAll('[role="alert"]')).toHaveLength(1)
- expect(document.querySelector('[role="alert"]')!.textContent).toContain('Fonts could not be loaded')
+})
+it('字体失败只提示一次,保留页面和刷新重试入口', () => {
+ watchFontLoading()
+ fonts.dispatchEvent(new Event('loadingerror')); fonts.dispatchEvent(new Event('loadingerror'))
+ expect(document.querySelectorAll('[role="alert"]')).toHaveLength(1)
+ expect(document.querySelector('[role="alert"]')!.textContent).toContain('Using fallback fonts')
expect(document.querySelector('[role="alert"] button')!.textContent).toBe('Refresh page')
- fonts.load()
- expect(document.documentElement.dataset.rmFonts).toBe('error')
-})
-
-it('开始监听前已有字体失败,也不能被 resolved ready 当作成功', async () => {
- fonts.faces = [{ status: 'error' }]
- fonts.finish()
- frame(0)
- await Promise.resolve()
- expect(document.documentElement.dataset.rmFonts).toBe('error')
- expect(document.querySelector('[role="alert"]')).not.toBeNull()
+ expect(document.documentElement.dataset.rmFonts).toBeUndefined()
})
diff --git a/web/src/common/appearance/font-loading.ts b/web/src/common/appearance/font-loading.ts
index a4c430b..35dfdf1 100644
--- a/web/src/common/appearance/font-loading.ts
+++ b/web/src/common/appearance/font-loading.ts
@@ -1,13 +1,16 @@
import { t as translate } from '@@/i18n/runtime'
-// Use the font set requested by actual browser layout, covering initial render, note switches, and later glyphs.
-export function watchFontLoading() {
- const root = document.documentElement
- const fonts = document.fonts
- root.dataset.rmFonts = 'loading'
+const started = new WeakSet()
+
+// Fonts may finish after interaction starts; never hide or disable the application.
+export function watchFontLoading() {
+ const fonts = document.fonts
+ if (started.has(fonts)) return
+ started.add(fonts)
+ let reported = false
function failed() {
- if (root.dataset.rmFonts === 'error') return
- root.dataset.rmFonts = 'error'
+ if (reported) return
+ reported = true
const panel = document.createElement('div')
panel.className = 'rm-font-error'
panel.setAttribute('role', 'alert')
@@ -19,24 +22,20 @@ export function watchFontLoading() {
panel.append(message, retry)
document.body.append(panel)
}
-
- function ready() {
- if (root.dataset.rmFonts === 'error') return
- // fonts.ready also resolves after download failure; failure must not count as fonts being ready.
- if ([...fonts].some(font => font.status === 'error')) failed()
- else if (fonts.status === 'loaded') root.dataset.rmFonts = 'ready'
- }
-
- fonts.addEventListener('loading', () => {
- if (root.dataset.rmFonts === 'error') return
- root.dataset.rmFonts = 'loading'
- void fonts.ready.then(ready)
- })
fonts.addEventListener('loadingerror', failed)
- // Trigger layout after mounting so the wait covers fonts used by the current text.
+ async function preload() {
+ const regular = [...fonts].filter(face => face.family.replace(/['"]/g, '') === 'Maple Mono Normal NL NF CN' && face.style === 'normal' && face.weight === '400')
+ for (let i = 0; i < regular.length; i += 2) {
+ const results = await Promise.allSettled(regular.slice(i, i + 2).map(face => face.load()))
+ if (results.some(result => result.status === 'rejected')) failed()
+ }
+ }
+ // Yield a paint before scheduling background downloads, including in the standalone demo.
requestAnimationFrame(() => {
- document.body.getBoundingClientRect()
- void fonts.ready.then(ready)
+ setTimeout(() => {
+ if ([...fonts].some(face => face.status === 'error')) failed()
+ void preload()
+ }, 0)
})
}
diff --git a/web/src/common/appearance/fonts.css b/web/src/common/appearance/fonts.css
index aa69da5..29746ca 100644
--- a/web/src/common/appearance/fonts.css
+++ b/web/src/common/appearance/fonts.css
@@ -3,7 +3,7 @@
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400 600;
- font-display: block;
+ font-display: swap;
src: url(./fonts/6b5e37b52d26eeac.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@@ -12,7 +12,7 @@
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400 600;
- font-display: block;
+ font-display: swap;
src: url(./fonts/eb83d8d40af4411f.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@@ -21,7 +21,7 @@
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400 600;
- font-display: block;
+ font-display: swap;
src: url(./fonts/99e400330f36ae77.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@@ -30,7 +30,7 @@
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400 600;
- font-display: block;
+ font-display: swap;
src: url(./fonts/c4064400db3750ce.woff2) format('woff2');
unicode-range:
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309,
@@ -41,7 +41,7 @@
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400 600;
- font-display: block;
+ font-display: swap;
src: url(./fonts/b19c2080652a3c2b.woff2) format('woff2');
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
@@ -52,7 +52,7 @@
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400 600;
- font-display: block;
+ font-display: swap;
src: url(./fonts/cc27b30471250057.woff2) format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
@@ -63,7 +63,7 @@
font-family: 'Manrope';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/842459da81f909f2.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@@ -72,7 +72,7 @@
font-family: 'Manrope';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/9b2d027172a3481c.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@@ -81,7 +81,7 @@
font-family: 'Manrope';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/ed7393f42d27db3d.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@@ -90,7 +90,7 @@
font-family: 'Manrope';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/1f3ea86e40d947e2.woff2) format('woff2');
unicode-range:
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309,
@@ -101,7 +101,7 @@
font-family: 'Manrope';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/ee75508933e6fd45.woff2) format('woff2');
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
@@ -112,7 +112,7 @@
font-family: 'Manrope';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/4872f2a6dc9a8250.woff2) format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
@@ -122,7 +122,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/7b3ddaeea40a0fe5.woff2) format('woff2');
unicode-range:
U+1f1e9-1f1f5, U+1f1f7-1f1ff, U+1f21a, U+1f232, U+1f234-1f237, U+1f250-1f251, U+1f300, U+1f302-1f308, U+1f30a-1f311,
@@ -132,7 +132,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/9af011928c669ba6.woff2) format('woff2');
unicode-range:
U+fee3, U+fef3, U+ff03-ff04, U+ff07, U+ff0a, U+ff17-ff19, U+ff1c-ff1d, U+ff20-ff3a, U+ff3c, U+ff3e-ff5b, U+ff5d,
@@ -144,7 +144,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/5f6c87a981ef2def.woff2) format('woff2');
unicode-range:
U+f0a7, U+f0b2, U+f0b7, U+f0c9, U+f0d8, U+f0da, U+f0dc-f0dd, U+f0e0, U+f0e6, U+f0eb, U+f0fc, U+f101, U+f104-f105,
@@ -159,7 +159,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/8953b4246f39fce9.woff2) format('woff2');
unicode-range:
U+9f83, U+9f85-9f8d, U+9f90-9f91, U+9f94-9f96, U+9f98, U+9f9b-9f9c, U+9f9e, U+9fa0, U+9fa2, U+9ff0-9fff, U+a001,
@@ -175,7 +175,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/e55b19a3912a75d1.woff2) format('woff2');
unicode-range:
U+9e30-9e33, U+9e35-9e3b, U+9e3e, U+9e40-9e44, U+9e46-9e4e, U+9e51, U+9e53, U+9e55-9e58, U+9e5a-9e5c, U+9e5e-9e63,
@@ -190,7 +190,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/935e2cf08e92673e.woff2) format('woff2');
unicode-range:
U+9c82-9c83, U+9c85-9c8c, U+9c8e-9c92, U+9c94-9c9b, U+9c9e-9ca3, U+9ca5-9ca7, U+9ca9, U+9cab, U+9cad-9cae,
@@ -204,7 +204,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/b5ae0757f6ecaa47.woff2) format('woff2');
unicode-range:
U+9a80, U+9a83, U+9a85, U+9a88-9a8a, U+9a8d-9a8e, U+9a90, U+9a92-9a93, U+9a95-9a96, U+9a98-9a99, U+9a9b-9aa2,
@@ -220,7 +220,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/4e47f619496a388f.woff2) format('woff2');
unicode-range:
U+98dd, U+98e1-98e2, U+98e7-98ea, U+98ec, U+98ee-98ef, U+98f2, U+98f4, U+98fc-98fe, U+9903, U+9905, U+9908, U+990a,
@@ -236,7 +236,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/3bc0783456e12fcc.woff2) format('woff2');
unicode-range:
U+975b-975c, U+9763, U+9765-9766, U+976c-976d, U+9773, U+9776, U+977a, U+977c, U+9784-9785, U+978e-978f,
@@ -252,7 +252,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/4168a698bef8858e.woff2) format('woff2');
unicode-range:
U+95c4-95ca, U+95cc-95cd, U+95d4-95d6, U+95d8, U+95e1-95e2, U+95e9, U+95f0-95f1, U+95f3, U+95f6, U+95fc,
@@ -268,7 +268,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/7a0b612893faf498.woff2) format('woff2');
unicode-range:
U+94f5, U+94f7, U+94f9, U+94fb-94fd, U+94ff, U+9503-9504, U+9506-9507, U+9509-950a, U+950d-950f, U+9511-9518,
@@ -280,7 +280,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/95d37f56ac503320.woff2) format('woff2');
unicode-range:
U+941c-942b, U+942d-942e, U+9432-9433, U+9435, U+9438, U+943a, U+943e, U+9444, U+944a, U+9451-9452, U+945a,
@@ -291,7 +291,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/7e74d2ffc02d78d0.woff2) format('woff2');
unicode-range:
U+92ec-92ed, U+92f0, U+92f3, U+92f8, U+92fc, U+9304, U+9306, U+9310, U+9312, U+9315, U+9318, U+931a, U+931e,
@@ -304,7 +304,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/70a0301ffa863394.woff2) format('woff2');
unicode-range:
U+9163-9164, U+9169, U+9170, U+9172, U+9174, U+9179-917a, U+917d-917e, U+9182-9183, U+9185, U+918c-918d,
@@ -320,7 +320,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/f65e41fe796f55cb.woff2) format('woff2');
unicode-range:
U+9004, U+900b, U+9011, U+9015-9016, U+901e, U+9021, U+9026, U+902d, U+902f, U+9031, U+9035-9036, U+9039-903a,
@@ -336,7 +336,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/1a993e7ab856f9c8.woff2) format('woff2');
unicode-range:
U+8e41-8e42, U+8e47, U+8e49-8e4b, U+8e50-8e53, U+8e59-8e5a, U+8e5f-8e60, U+8e64, U+8e69, U+8e6c, U+8e70, U+8e74,
@@ -352,7 +352,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/50a18da9b4927dbb.woff2) format('woff2');
unicode-range:
U+8cbd, U+8cbf-8cc4, U+8cc7-8cc8, U+8cca, U+8ccd, U+8cd1, U+8cd3, U+8cdb-8cdc, U+8cde, U+8ce0, U+8ce2-8ce4,
@@ -368,7 +368,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/a806fd74a8216e5f.woff2) format('woff2');
unicode-range:
U+8b80, U+8b83, U+8b8a, U+8b8c, U+8b90, U+8b93, U+8b99-8b9a, U+8ba0, U+8ba3, U+8ba5-8ba7, U+8baa-8bac, U+8bb4-8bb5,
@@ -383,7 +383,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/78bd32786a1b31ff.woff2) format('woff2');
unicode-range:
U+8a15-8a18, U+8a1a-8a1b, U+8a1d, U+8a1f, U+8a22-8a23, U+8a25, U+8a2b, U+8a2d, U+8a31, U+8a33-8a34, U+8a36-8a38,
@@ -399,7 +399,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/f44e78d8ce83767d.woff2) format('woff2');
unicode-range:
U+8882, U+8884-8886, U+8888, U+888f, U+8892-8893, U+889b, U+88a2, U+88a4, U+88a6, U+88a8, U+88aa, U+88ae, U+88b1,
@@ -415,7 +415,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/63e1e25c88076e71.woff2) format('woff2');
unicode-range:
U+86f4, U+86f8-86f9, U+86fb, U+86fe, U+8703, U+8706-870a, U+870d, U+8711-8713, U+871a, U+871e, U+8722-8723, U+8725,
@@ -431,7 +431,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/badb08d4ed31aab3.woff2) format('woff2');
unicode-range:
U+8548, U+854e, U+8553, U+8556-8557, U+8559, U+855e, U+8561, U+8564-8565, U+8568-856a, U+856d, U+856f-8570, U+8572,
@@ -447,7 +447,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/9d0550296822bc88.woff2) format('woff2');
unicode-range:
U+83c5, U+83c8-83c9, U+83cb, U+83d1, U+83d3-83d6, U+83d8, U+83db, U+83dd, U+83df, U+83e1, U+83e5, U+83ea-83eb,
@@ -464,7 +464,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/ea65d613de6262b7.woff2) format('woff2');
unicode-range:
U+82bc, U+82be, U+82c0-82c2, U+82c4-82c8, U+82ca-82cc, U+82ce, U+82d0, U+82d2-82d3, U+82d5-82d6, U+82d8-82d9,
@@ -479,7 +479,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/f624e6edcc42557f.woff2) format('woff2');
unicode-range:
U+8166-8169, U+816b, U+816d, U+8171, U+8173-8174, U+8178, U+817c-817d, U+8182, U+8188, U+8191, U+8198-819b, U+81a0,
@@ -495,7 +495,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/af97bfb909af51ae.woff2) format('woff2');
unicode-range:
U+8016, U+8018-8019, U+801c, U+801e, U+8026-802a, U+8031, U+8034-8035, U+8037, U+8043, U+804b, U+804d, U+8052,
@@ -510,7 +510,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/84a684f5b97032b6.woff2) format('woff2');
unicode-range:
U+7ef0-7ef2, U+7ef6, U+7efa-7efb, U+7efe, U+7f01-7f04, U+7f08, U+7f0a-7f12, U+7f17, U+7f19, U+7f1b-7f1c, U+7f1f,
@@ -525,7 +525,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/4af13061394e55b3.woff2) format('woff2');
unicode-range:
U+7dd2, U+7dd4, U+7dd6-7dd8, U+7dda-7de0, U+7de2-7de6, U+7de8-7ded, U+7def, U+7df1-7df5, U+7df7, U+7df9,
@@ -540,7 +540,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/a3739966a326e377.woff2) format('woff2');
unicode-range:
U+7ce8, U+7cec, U+7cf0, U+7cf5-7cf9, U+7cfc, U+7cfe, U+7d00, U+7d04-7d0b, U+7d0d, U+7d10-7d14, U+7d17-7d19,
@@ -554,7 +554,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/6c7df51c2d3405f8.woff2) format('woff2');
unicode-range:
U+7bd3-7bd4, U+7bd9-7bda, U+7bdd, U+7be0-7be1, U+7be4-7be6, U+7be9-7bea, U+7bef, U+7bf4, U+7bf6, U+7bfc, U+7bfe,
@@ -568,7 +568,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/a8901603a8f60b5c.woff2) format('woff2');
unicode-range:
U+7ae6, U+7af4-7af7, U+7afa-7afb, U+7afd-7b0a, U+7b0c, U+7b0e-7b0f, U+7b13, U+7b15-7b16, U+7b18-7b19, U+7b1e-7b20,
@@ -581,7 +581,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/62d567322492159f.woff2) format('woff2');
unicode-range:
U+798b-798e, U+7992, U+7994-7995, U+7997-7998, U+799a-799c, U+799f, U+79a3-79a6, U+79a8-79ac, U+79ae-79b1,
@@ -597,7 +597,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/4ca36ec0898d9f4b.woff2) format('woff2');
unicode-range:
U+784c, U+784e-7854, U+7856-7857, U+7859-785a, U+7865, U+7869-786a, U+786d, U+786f, U+7876-7877, U+787c,
@@ -613,7 +613,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/dc00089f866f9369.woff2) format('woff2');
unicode-range:
U+7722, U+7726, U+7728, U+772b-7730, U+7732-7736, U+7739-773a, U+773d-773f, U+7743, U+7746-7747, U+774c-774f,
@@ -628,7 +628,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/b14a1ed0396f00cf.woff2) format('woff2');
unicode-range:
U+7613-7619, U+761b-761d, U+761f-7622, U+7625, U+7627-762a, U+762e-7630, U+7632-7635, U+7638-763a, U+763c-763d,
@@ -643,7 +643,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/beed123d60cc6155.woff2) format('woff2');
unicode-range:
U+750d, U+750f, U+7511, U+7513, U+7515, U+7517, U+7519, U+7521-7527, U+752a, U+752c-752d, U+752f, U+7534, U+7536,
@@ -658,7 +658,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/9fcc77355f5a0d52.woff2) format('woff2');
unicode-range:
U+73f0, U+73f2, U+73f4-73f5, U+73f7, U+73f9-73fa, U+73fc-73fd, U+73ff-7402, U+7404, U+7407-7408, U+740a-740f,
@@ -673,7 +673,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/500793f7237d7337.woff2) format('woff2');
unicode-range:
U+72e6, U+72e8, U+72ef-72f0, U+72f2-72f4, U+72f6-72f7, U+72f9-72fb, U+72fd, U+7300-7304, U+7307, U+730a-730c,
@@ -688,7 +688,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/c4c44fadfc63ac54.woff2) format('woff2');
unicode-range:
U+71a8, U+71af, U+71b1-71bc, U+71be, U+71c1-71c2, U+71c4, U+71c8-71cb, U+71ce-71d0, U+71d2, U+71d4, U+71d9-71da,
@@ -703,7 +703,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/cf9cd646bc0d0f19.woff2) format('woff2');
unicode-range:
U+700b, U+700d, U+7015, U+7018, U+701b, U+701d-701f, U+7023, U+7026-7028, U+702c, U+702e-7030, U+7035, U+7037,
@@ -720,7 +720,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/620f5f28321be052.woff2) format('woff2');
unicode-range:
U+6ed9, U+6edb, U+6edd, U+6edf-6ee0, U+6ee2, U+6ee6, U+6eea, U+6eec, U+6eee-6eef, U+6ef2-6ef3, U+6ef7-6efa, U+6efe,
@@ -735,7 +735,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/658210c7f683a557.woff2) format('woff2');
unicode-range:
U+6dc3, U+6dc5-6dc6, U+6dc9, U+6dcc, U+6dcf, U+6dd2-6dd3, U+6dd6, U+6dd9-6dde, U+6de0, U+6de4, U+6de6, U+6de8-6dea,
@@ -750,7 +750,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/795b87224e8dcbc4.woff2) format('woff2');
unicode-range:
U+6cb1-6cb2, U+6cb4-6cb5, U+6cb7, U+6cba, U+6cbc-6cbd, U+6cc1-6cc3, U+6cc5-6cc7, U+6cd0-6cd4, U+6cd6-6cd7,
@@ -765,7 +765,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/e9d83a6fe76066e1.woff2) format('woff2');
unicode-range:
U+6b83-6b86, U+6b89, U+6b8d, U+6b91-6b93, U+6b95, U+6b97-6b98, U+6b9a-6b9b, U+6b9e, U+6ba1-6ba4, U+6ba9-6baa,
@@ -780,7 +780,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/df78bcc0618fdcb2.woff2) format('woff2');
unicode-range:
U+69fe-6a01, U+6a06, U+6a09, U+6a0b, U+6a11, U+6a13, U+6a17-6a19, U+6a1b, U+6a1e, U+6a23, U+6a28-6a29, U+6a2b,
@@ -796,7 +796,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/cf0b566ed87b1702.woff2) format('woff2');
unicode-range:
U+68d3, U+68d7, U+68dd, U+68df, U+68e1, U+68e3-68e4, U+68e6-68ed, U+68ef-68f0, U+68f2, U+68f4, U+68f6-68f7, U+68f9,
@@ -811,7 +811,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/b4bf867aff4a6bde.woff2) format('woff2');
unicode-range:
U+678b-678d, U+678f, U+6792-6793, U+6796, U+6798, U+679e-67a1, U+67a5, U+67a7-67a9, U+67ac-67ad, U+67b0-67b1,
@@ -827,7 +827,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/23f09a3fd29c803a.woff2) format('woff2');
unicode-range:
U+6631, U+6633-6634, U+6636, U+663a-663b, U+663d, U+6641, U+6644-6645, U+6649, U+664c, U+664f, U+6654, U+6659,
@@ -843,7 +843,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/e110453047bfe034.woff2) format('woff2');
unicode-range:
U+64f1-64f2, U+64f4, U+64f7-64f8, U+64fa, U+64fc, U+64fe-64ff, U+6503, U+6509, U+650f, U+6514, U+6518, U+651c-651e,
@@ -859,7 +859,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/b7668e18fe9cce6c.woff2) format('woff2');
unicode-range:
U+63b8-63bc, U+63be, U+63c0, U+63c3-63c4, U+63c6, U+63c8, U+63cd-63ce, U+63d1, U+63d6, U+63da-63db, U+63de, U+63e0,
@@ -874,7 +874,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/2306716a58a58a05.woff2) format('woff2');
unicode-range:
U+622c, U+622e-6230, U+6232, U+6238, U+623b, U+623d-623e, U+6243, U+6246, U+6248-6249, U+624c, U+6255, U+6259,
@@ -890,7 +890,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/b37a954f47c08e8e.woff2) format('woff2');
unicode-range:
U+60ed-60ee, U+60f0-60f1, U+60f4, U+60f6, U+60fa, U+6100, U+6106, U+610d-610e, U+6112, U+6114-6115, U+6119, U+611c,
@@ -905,7 +905,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/41d1d19f7bebcf26.woff2) format('woff2');
unicode-range:
U+5fc4, U+5fc9, U+5fcb, U+5fce-5fd6, U+5fda-5fde, U+5fe1-5fe2, U+5fe4-5fe5, U+5fea, U+5fed-5fee, U+5ff1-5ff3,
@@ -920,7 +920,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/3c66c61b08f9dc28.woff2) format('woff2');
unicode-range:
U+5e98, U+5e9b, U+5e9d, U+5ea0-5ea5, U+5ea8, U+5eab, U+5eaf, U+5eb3, U+5eb5-5eb6, U+5eb9, U+5ebe, U+5ec1-5ec3,
@@ -935,7 +935,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/8a51ce32a2d6a2b0.woff2) format('woff2');
unicode-range:
U+5d26-5d27, U+5d2e-5d34, U+5d3c-5d3e, U+5d41-5d44, U+5d46-5d48, U+5d4a-5d4b, U+5d4e, U+5d50, U+5d52, U+5d55-5d58,
@@ -950,7 +950,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/ffa6ae4b9eb5ffdc.woff2) format('woff2');
unicode-range:
U+5bec, U+5bee-5bf0, U+5bf2-5bf3, U+5bf5-5bf6, U+5bfe, U+5c02-5c03, U+5c05, U+5c07-5c09, U+5c0b-5c0c, U+5c0e,
@@ -965,7 +965,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/96088f438d29f54b.woff2) format('woff2');
unicode-range:
U+5aa0, U+5aa3-5aa4, U+5aaa, U+5aae-5aaf, U+5ab1-5ab2, U+5ab4-5ab5, U+5ab7-5aba, U+5abd-5abf, U+5ac3-5ac4,
@@ -980,7 +980,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/e0d8fdb665a16cd2.woff2) format('woff2');
unicode-range:
U+596a, U+596c-596e, U+5977, U+597b-597c, U+5981, U+598f, U+5997-5998, U+599a, U+599c-599d, U+59a0-59a1,
@@ -995,7 +995,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/20f6ba8c0c269b1a.woff2) format('woff2');
unicode-range:
U+5820, U+5822-5823, U+5825-5826, U+582c, U+582f, U+5831, U+583a, U+583d, U+583f-5842, U+5844-5846, U+5848, U+584a,
@@ -1011,7 +1011,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/847818b9c19663fd.woff2) format('woff2');
unicode-range:
U+56f9, U+56fc, U+56ff-5700, U+5703-5704, U+5709-570a, U+570c-570d, U+570f, U+5712-5713, U+5718-5719, U+571c,
@@ -1026,7 +1026,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/a7c81fbdd8cc5eb5.woff2) format('woff2');
unicode-range:
U+55f5-55f7, U+55fb, U+55fe, U+5600-5601, U+5605-5606, U+5608, U+560c-560d, U+560f, U+5614, U+5616-5617, U+561a,
@@ -1040,7 +1040,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/23acd90334da40b5.woff2) format('woff2');
unicode-range:
U+550f, U+5511-5514, U+5516-5517, U+5519, U+551b, U+551d-551e, U+5520, U+5522-5523, U+5526-5527, U+552a-552c,
@@ -1054,7 +1054,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/aebf42c37a635878.woff2) format('woff2');
unicode-range:
U+53e7-53e9, U+53f1, U+53f4-53f5, U+53fa-5400, U+5402, U+5405-5407, U+540b, U+540f, U+5412, U+5414, U+5416,
@@ -1069,7 +1069,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/c08b4b4d95cfb2ea.woff2) format('woff2');
unicode-range:
U+5289, U+528b, U+528d, U+528f, U+5291-5293, U+529a, U+52a2, U+52a6-52a7, U+52ac-52ad, U+52af, U+52b4-52b5, U+52b9,
@@ -1085,7 +1085,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/5b4fc0b27a8a1464.woff2) format('woff2');
unicode-range:
U+5104, U+5106-5107, U+5109-510b, U+510d, U+510f-5110, U+5113, U+5115, U+5117-5118, U+511a-511c, U+511e-511f,
@@ -1101,7 +1101,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/14213d7fbd6ab158.woff2) format('woff2');
unicode-range:
U+4fd1, U+4fd3, U+4fda-4fdc, U+4fdf-4fe0, U+4fe2-4fe4, U+4fe6, U+4fe8, U+4feb-4fed, U+4ff3, U+4ff5-4ff6, U+4ff8,
@@ -1116,7 +1116,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/3ff1abbbffc86b31.woff2) format('woff2');
unicode-range:
U+4ea3, U+4ea5, U+4eb0-4eb1, U+4eb3-4eb6, U+4eb8-4eb9, U+4ebb-4ebe, U+4ec2-4ec4, U+4ec8-4ec9, U+4ecc, U+4ecf-4ed0,
@@ -1131,7 +1131,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/5e6011e3010d50bd.woff2) format('woff2');
unicode-range:
U+3129, U+3131, U+3134, U+3137, U+3139, U+3141-3142, U+3145, U+3147-3148, U+314b, U+314d-314e, U+315c, U+3160-3161,
@@ -1147,7 +1147,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/65a11622c84af2c7.woff2) format('woff2');
unicode-range:
U+279f-27a2, U+27a4-27a5, U+27a8, U+27b0, U+27b2-27b3, U+27b9, U+27e8-27e9, U+27f6, U+2800, U+28ec, U+2913,
@@ -1162,7 +1162,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/6d4e9520e29f6d02.woff2) format('woff2');
unicode-range:
U+2651-2655, U+2658, U+265a-265b, U+265d-265e, U+2660-266d, U+266f, U+267b, U+2688, U+2693-2696, U+2698-2699,
@@ -1175,7 +1175,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/497141c2daba02b0.woff2) format('woff2');
unicode-range:
U+2550-2551, U+2554, U+2557, U+255a-255b, U+255d, U+255f-2560, U+2562-2563, U+2565-2567, U+2569-256a, U+256c-2572,
@@ -1187,7 +1187,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/eb4969d421026bd0.woff2) format('woff2');
unicode-range:
U+23f0, U+23f3, U+2445, U+2449, U+2465-2471, U+2474-249b, U+24b8, U+24c2, U+24c7, U+24c9, U+24d0, U+24d2, U+24d4,
@@ -1198,7 +1198,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/48c235f25cd265a1.woff2) format('woff2');
unicode-range:
U+207c-2083, U+208c-208e, U+2092, U+20a6, U+20a8-20ad, U+20af, U+20b1, U+20b4-20b5, U+20b8-20ba, U+20bd, U+20db,
@@ -1213,7 +1213,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/0a8cf734a0b6b153.woff2) format('woff2');
unicode-range:
U+1d34-1d35, U+1d38-1d3a, U+1d3c, U+1d3f-1d40, U+1d49, U+1d4e-1d4f, U+1d52, U+1d55, U+1d5b, U+1d5e, U+1d9c, U+1da0,
@@ -1227,7 +1227,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/b42723fe53599b90.woff2) format('woff2');
unicode-range:
U+2ae-2b3, U+2b5-2bf, U+2c2-2c3, U+2c6-2d1, U+2d8-2da, U+2dc, U+2e1-2e3, U+2e5, U+2eb, U+2ee-2f0, U+2f2-2f7,
@@ -1239,7 +1239,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/05a7d600a249b837.woff2) format('woff2');
unicode-range:
U+176-17f, U+192, U+194, U+19a-19b, U+19d, U+1a0-1a1, U+1a3-1a4, U+1aa, U+1ac-1ad, U+1af-1bf, U+1d2, U+1d4, U+1d6,
@@ -1252,7 +1252,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/b73283458ef26d55.woff2) format('woff2');
unicode-range:
U+a1-a4, U+a6-a8, U+aa, U+ac, U+af, U+b1, U+b3-b6, U+b8-ba, U+bc-d6, U+d8-de, U+e6, U+eb, U+ee-f0, U+f5, U+f7-f8,
@@ -1264,7 +1264,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/2944f60aea487c2a.woff2) format('woff2');
unicode-range:
U+221a, U+2264, U+2464, U+25a0, U+3008, U+4e10, U+512a, U+5152, U+5201, U+5241, U+5340, U+5352, U+549a, U+54b2,
@@ -1286,7 +1286,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/b140f9d60ba877f5.woff2) format('woff2');
unicode-range:
U+161, U+926, U+928, U+939, U+93f-940, U+94d, U+e17, U+e22, U+e44, U+25c7, U+25ce, U+2764, U+3009, U+3016-3017,
@@ -1307,7 +1307,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/3bf9956fe5c594f4.woff2) format('woff2');
unicode-range:
U+10c, U+627-629, U+639, U+644, U+64a, U+203b, U+2265, U+2463, U+2573, U+25b2, U+3448-3449, U+4e1e, U+4e5e, U+4f3a,
@@ -1329,7 +1329,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/47d3dc9f8403f684.woff2) format('woff2');
unicode-range:
U+60, U+631, U+2606, U+3014-3015, U+309c, U+33a1, U+4e52, U+4ec6, U+4f86, U+4f8d, U+4fde, U+4fef, U+500b, U+502a,
@@ -1351,7 +1351,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/4a7ea73dc15f2b63.woff2) format('woff2');
unicode-range:
U+2003, U+2193, U+2462, U+4e19, U+4e2b, U+4e36, U+4ea8, U+4ed1, U+4ed7, U+4f51, U+4f63, U+4f83, U+50e7, U+5112,
@@ -1373,7 +1373,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/f0721a3949f84d42.woff2) format('woff2');
unicode-range:
U+5e, U+2190, U+250a, U+25bc, U+25cf, U+4e56, U+4ea9, U+4f3d, U+4f6c, U+4f88, U+4fa8, U+4fcf, U+5029, U+5188,
@@ -1395,7 +1395,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/71139ee70c1b1fce.woff2) format('woff2');
unicode-range:
U+b0, U+2191, U+2460-2461, U+25c6, U+300e-300f, U+4e1b, U+4e7e, U+4ed5, U+4ef2, U+4f10, U+4f1e, U+4f50, U+4fa6,
@@ -1417,7 +1417,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/75c0090760a68ac5.woff2) format('woff2');
unicode-range:
U+200b, U+2103, U+4e18, U+4e27-4e28, U+4e38, U+4e59, U+4e8f, U+4ead, U+4ec7, U+4fe9, U+503a, U+5085, U+5146, U+51af,
@@ -1439,7 +1439,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/8eae1f2d9b73ca52.woff2) format('woff2');
unicode-range:
U+24, U+4e08, U+4e43, U+4e4f, U+4ef0, U+4f2a, U+507f, U+50ac, U+50bb, U+5151, U+51bb, U+51f6, U+51fd, U+5272,
@@ -1461,7 +1461,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/a263283eb6ff7679.woff2) format('woff2');
unicode-range:
U+a5, U+2192, U+2605, U+4e11, U+4e22, U+4e32, U+4f0d, U+4f0f, U+4f69, U+4ff1, U+50b2, U+5154, U+51dd, U+51f0,
@@ -1483,7 +1483,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/09c325c9da420816.woff2) format('woff2');
unicode-range:
U+300c-300d, U+4e54, U+4e58, U+4e95, U+4ec1, U+4f2f, U+4f38, U+4fa3, U+4fca, U+503e, U+5141, U+5144, U+517c, U+51cc,
@@ -1505,7 +1505,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/053609e6b57c594f.woff2) format('woff2');
unicode-range:
U+4e4c, U+4e88, U+4ea1, U+4ea6, U+4ed3-4ed4, U+4eff, U+4f30, U+4fa7, U+4fc4, U+4fd7, U+500d, U+504f, U+5076-5077,
@@ -1527,7 +1527,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/5c92b2016607caeb.woff2) format('woff2');
unicode-range:
U+23, U+3d, U+4e01, U+4e39, U+4e73, U+4ecd, U+4ed9, U+4eea, U+4f0a, U+4f1f, U+4f5b, U+4fa0, U+4fc3, U+501f, U+50a8,
@@ -1548,7 +1548,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/f192aec3fa1f17f4.woff2) format('woff2');
unicode-range:
U+26, U+3c, U+d7, U+4e4e, U+4e61, U+4e71, U+4ebf, U+4f26, U+5012, U+51ac, U+51b0, U+51b2, U+51b7, U+5218, U+521a,
@@ -1569,7 +1569,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/f6255320a8ecc997.woff2) format('woff2');
unicode-range:
U+7e, U+2026, U+4e03, U+4e25, U+4e30, U+4e34, U+4e45, U+4e5d, U+4e89, U+4eae, U+4ed8, U+4f11, U+4f19, U+4f24,
@@ -1591,7 +1591,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/5849283072442ca2.woff2) format('woff2');
unicode-range:
U+25, U+4e14, U+4e1d, U+4e3d, U+4e49, U+4e60, U+4e9a, U+4eb2, U+4ec5, U+4efd, U+4f3c, U+4f4f, U+4f8b, U+4fbf,
@@ -1613,7 +1613,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/04eaf1a999368419.woff2) format('woff2');
unicode-range:
U+2b, U+40, U+3000, U+300a-300b, U+4e16, U+4e66, U+4e70, U+4e91-4e92, U+4e94, U+4e9b, U+4ec0, U+4eca, U+4f01,
@@ -1635,7 +1635,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/2ef4d8c6c25aaa84.woff2) format('woff2');
unicode-range:
U+4e, U+201c-201d, U+3010-3011, U+4e07, U+4e1c, U+4e24, U+4e3e, U+4e48, U+4e50, U+4e5f, U+4e8b-4e8c, U+4ea4,
@@ -1657,7 +1657,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/f63d970a7b2b5321.woff2) format('woff2');
unicode-range:
U+d, U+3e, U+5f, U+7c, U+a0, U+a9, U+4e09-4e0b, U+4e0d-4e0e, U+4e13, U+4e1a, U+4e2a, U+4e3a-4e3b, U+4e4b, U+4e86,
@@ -1678,7 +1678,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/ca6307f70603bdbf.woff2) format('woff2');
unicode-range:
U+20-22, U+27-2a, U+2c-3b, U+3f, U+41-4d, U+4f-5d, U+61-7b, U+7d, U+ab, U+ae, U+b2, U+b7, U+bb, U+df-e5, U+e7-ea,
@@ -1694,7 +1694,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/89c8b72714b2fb05.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@@ -1703,7 +1703,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/38475678e2d17cfd.woff2) format('woff2');
unicode-range:
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309,
@@ -1714,7 +1714,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/59aba4ce37e0d3de.woff2) format('woff2');
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
@@ -1725,7 +1725,7 @@
font-family: 'Noto Serif SC';
font-style: normal;
font-weight: 400 700;
- font-display: block;
+ font-display: swap;
src: url(./fonts/d8c61f0c834f348a.woff2) format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
diff --git a/web/src/common/appearance/fonts.ts b/web/src/common/appearance/fonts.ts
index 5aa3c9a..42e04c3 100644
--- a/web/src/common/appearance/fonts.ts
+++ b/web/src/common/appearance/fonts.ts
@@ -14,7 +14,7 @@ export function applyFontCatalog(fonts: CustomFont[]) {
style = document.createElement('style'); style.id = 'rm-custom-fonts'; document.head.append(style)
}
// Names are display-only; CSS identifiers and resource paths use only server-generated content hashes.
- const css = fonts.map(font => `@font-face { font-family: '${font.id}'; src: url('${fontResourceRoot()}${font.id}/file'); font-display: block; }`).join('\n')
+ const css = fonts.map(font => `@font-face { font-family: '${font.id}'; src: url('${fontResourceRoot()}${font.id}/file'); font-display: swap; }`).join('\n')
if (style.textContent !== css) style.textContent = css
}
diff --git a/web/src/common/appearance/maple-font.css b/web/src/common/appearance/maple-font.css
index 2c544d5..de3c167 100644
--- a/web/src/common/appearance/maple-font.css
+++ b/web/src/common/appearance/maple-font.css
@@ -1,61 +1,61 @@
/* Maple Mono v7.9; generated by scripts/prepare-maple-font.py. See fonts/maple/LICENSE.txt for licensing. */
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-00.woff2') format('woff2'); unicode-range: U+0-FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-00.woff2') format('woff2'); unicode-range: U+0-FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-00.woff2') format('woff2'); unicode-range: U+0-FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-00.woff2') format('woff2'); unicode-range: U+0-FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; }
-@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-00.woff2') format('woff2'); unicode-range: U+0-FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-00.woff2') format('woff2'); unicode-range: U+0-FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-00.woff2') format('woff2'); unicode-range: U+0-FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-00.woff2') format('woff2'); unicode-range: U+0-FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; }
+@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; }
diff --git a/web/src/common/i18n/en.json b/web/src/common/i18n/en.json
index d18db92..3cd05ad 100644
--- a/web/src/common/i18n/en.json
+++ b/web/src/common/i18n/en.json
@@ -1,7 +1,7 @@
{
"demo_notice": "Editor preview only. Changes are not saved; refreshing restores the sample.",
"retry_b8784c": "Retry",
- "fonts_could_not_be_loaded_42a574": "Fonts could not be loaded. Check your connection and refresh the page.",
+ "fonts_could_not_be_loaded_42a574": "Some fonts could not be loaded. Using fallback fonts; refresh to try again.",
"refresh_page_9366e1": "Refresh page",
"invalid_font_catalog_b577df": "Invalid font catalog",
"manrope_sans_serif_a8923c": "Manrope · Sans serif",
diff --git a/web/src/common/i18n/zh-CN.json b/web/src/common/i18n/zh-CN.json
index 6f324cc..325836e 100644
--- a/web/src/common/i18n/zh-CN.json
+++ b/web/src/common/i18n/zh-CN.json
@@ -1,7 +1,7 @@
{
"demo_notice": "仅供编辑器预览。修改不会保存,刷新后恢复示例。",
"retry_b8784c": "重试",
- "fonts_could_not_be_loaded_42a574": "字体加载失败,请检查网络后刷新页面。",
+ "fonts_could_not_be_loaded_42a574": "部分字体加载失败,正在使用备用字体,可刷新页面重试。",
"refresh_page_9366e1": "刷新页面",
"invalid_font_catalog_b577df": "字体列表格式无效",
"manrope_sans_serif_a8923c": "Manrope · 清晰无衬线",
diff --git a/web/src/main.ts b/web/src/main.ts
index 2755a17..5f0d586 100644
--- a/web/src/main.ts
+++ b/web/src/main.ts
@@ -13,7 +13,6 @@ import '@@/assets/styles/app.css'
import '@@/assets/styles/element-plus.scss'
import 'virtual:uno.css'
-watchFontLoading()
const app = createApp(App)
app.use(i18n).use(pinia).use(router)
const user = useUserStore(pinia)
@@ -28,5 +27,5 @@ watch(() => user.sessionVersion, () => {
})
// Mount a retryable error interface even if identity restoration temporarily fails.
void router.isReady().catch(() => {}).finally(() => {
- app.mount('#app'); void registerPwa()
+ app.mount('#app'); watchFontLoading(); void registerPwa()
})
diff --git a/web/src/pages/workspace/components/EditorPane.vue b/web/src/pages/workspace/components/EditorPane.vue
index 3f33212..cd9d584 100644
--- a/web/src/pages/workspace/components/EditorPane.vue
+++ b/web/src/pages/workspace/components/EditorPane.vue
@@ -74,6 +74,9 @@ watch(() => [ws.active, tab.value?.status, ws.view, ws.images], () => {
{{ translate('workspace.select_or_create_a_note_669f82') }}
+
+
{{ translate('workspace.loading_4927a5') }}
+
{{ translate('workspace.could_not_open_this_note_634236') }}
diff --git a/web/src/service-worker.js b/web/src/service-worker.js
index 5d12cbd..55ec087 100644
--- a/web/src/service-worker.js
+++ b/web/src/service-worker.js
@@ -1,13 +1,15 @@
const CACHE = 'rawmark-shell-__VERSION__'
const FILES = __PRECACHE__
const paths = new Set(FILES)
+const isFont = path => /\.(?:woff2?|ttf|otf)$/.test(path)
+const shell = FILES.filter(path => !isFont(path))
globalThis.addEventListener('install', (event) => {
event.waitUntil((async () => {
const cache = await caches.open(CACHE)
- // Download in batches instead of requesting hundreds of syntax packages and fonts concurrently.
- for (let i = 0; i < FILES.length; i += 16) {
- await cache.addAll(FILES.slice(i, i + 16).map(url => new Request(url, { cache: 'reload' })))
+ // Download in batches instead of requesting hundreds of syntax packages concurrently.
+ for (let i = 0; i < shell.length; i += 16) {
+ await cache.addAll(shell.slice(i, i + 16).map(url => new Request(url, { cache: 'reload' })))
}
})())
})
@@ -67,6 +69,11 @@ globalThis.addEventListener('fetch', (event) => {
if (previous) return previous
}
}
- return fetch(request)
+ const response = await fetch(request)
+ // Cache public build fonts only after use; private resource tickets never reach this branch.
+ if (paths.has(url.pathname) && isFont(url.pathname) && response.ok) {
+ event.waitUntil(cache.put(url.pathname, response.clone()))
+ }
+ return response
})())
})
diff --git a/web/src/service-worker.test.ts b/web/src/service-worker.test.ts
new file mode 100644
index 0000000..c2b9e37
--- /dev/null
+++ b/web/src/service-worker.test.ts
@@ -0,0 +1,53 @@
+import { readFileSync } from 'node:fs'
+import { runInNewContext } from 'node:vm'
+import { expect, it, vi } from 'vitest'
+
+function worker() {
+ const handlers: Record) => void, respondWith: (promise: Promise) => void }) => void> = {}
+ const cache = { addAll: vi.fn().mockResolvedValue(undefined), match: vi.fn<() => Promise>().mockResolvedValue(undefined), put: vi.fn().mockResolvedValue(undefined) }
+ const fetch = vi.fn().mockResolvedValue(new Response('font'))
+ const source = readFileSync(new URL('./service-worker.js', import.meta.url), 'utf8').replace('__VERSION__', 'test').replace('__PRECACHE__', JSON.stringify(['/index.html', '/assets/main.js', '/assets/regular.woff2', '/assets/bold.woff2']))
+ runInNewContext(source, { URL, Request: class extends Request {
+ constructor(url: string, options?: RequestInit) {
+ super(new URL(url, 'https://test.invalid'), options)
+ }
+ }, fetch, caches: { open: async () => cache, keys: async () => [] }, globalThis: { location: { origin: 'https://test.invalid' }, addEventListener: (name: string, handler: (event: { request: Request, waitUntil: (promise: Promise) => void, respondWith: (promise: Promise) => void }) => void) => {
+ handlers[name] = handler
+ } } })
+ return { handlers, cache, fetch }
+}
+it('安装缓存外壳但不预下载字体', async () => {
+ const { handlers, cache } = worker()
+ let done!: Promise
+ handlers.install!({ request: new Request('https://test.invalid'), respondWith: vi.fn(), waitUntil: (promise: Promise) => {
+ done = promise
+ } })
+ await done
+ expect(cache.addAll.mock.calls.flat(2).map((request: Request) => request.url)).toEqual(['https://test.invalid/index.html', 'https://test.invalid/assets/main.js'])
+})
+it('使用后缓存公开构建字体,缓存命中不再请求网络', async () => {
+ const { handlers, cache, fetch } = worker()
+ const pending: Promise[] = []
+ let response!: Promise
+ const event = { request: new Request('https://test.invalid/assets/bold.woff2'), respondWith: (promise: Promise) => {
+ response = promise
+ }, waitUntil: (promise: Promise) => pending.push(promise) }
+ handlers.fetch!(event); expect((await response).status).toBe(200); await Promise.all(pending)
+ expect(cache.put).toHaveBeenCalledOnce()
+ cache.match.mockResolvedValue(new Response('cached'))
+ handlers.fetch!(event); expect(await (await response).text()).toBe('cached')
+ expect(fetch).toHaveBeenCalledOnce()
+})
+it('不缓存失败响应,不拦截私有字体票据', async () => {
+ const { handlers, cache, fetch } = worker()
+ fetch.mockResolvedValue(new Response('missing', { status: 404 }))
+ let response!: Promise
+ const respondWith = vi.fn((promise: Promise) => {
+ response = promise
+ })
+ handlers.fetch!({ request: new Request('https://test.invalid/assets/regular.woff2'), respondWith, waitUntil: vi.fn() })
+ expect((await response).status).toBe(404); expect(cache.put).not.toHaveBeenCalled()
+ respondWith.mockClear()
+ handlers.fetch!({ request: new Request('https://test.invalid/api/v1/resources/ticket/fonts/custom/file'), respondWith, waitUntil: vi.fn() })
+ expect(respondWith).not.toHaveBeenCalled()
+})