diff --git a/tools/vendor-fonts.py b/tools/vendor-fonts.py
new file mode 100644
index 00000000..1e163cbf
--- /dev/null
+++ b/tools/vendor-fonts.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+"""Download the web fonts from Google Fonts and vendor them into the repo.
+
+Run by hand when the font set changes, never at build or run time:
+
+ python3 tools/vendor-fonts.py
+
+The app must render itself with no outbound network, so nothing here may
+happen while it is running. This script fetches once, writes the woff2 files
+and a stylesheet that points at them by RELATIVE url, and everything is
+committed. `web/static/` is copied verbatim into the SvelteKit build, which
+Go then embeds — so the fonts travel inside the binary.
+
+Relative urls in the stylesheet (`./Inter-latin.woff2`, not `/fonts/...`)
+are deliberate: a CSS url() resolves against the stylesheet's own address, so
+the whole directory keeps working when the app is served under a base path.
+
+Subsets are kept as Google slices them, with their `unicode-range` intact.
+That is not a size compromise — the browser downloads only the ranges a page
+actually uses, so vendoring every subset costs repository bytes rather than
+request bytes, and a library full of non-English artist names renders instead
+of falling back mid-list.
+"""
+import re
+import sys
+import urllib.request
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parent.parent
+OUT = ROOT / "web/static/fonts"
+
+# Matches the faces the design system actually permits: two weights only, 400
+# and 500 (never 600/700), and Fraunces' optical-size axis across its range.
+FAMILIES = (
+ "family=Fraunces:opsz,wght@9..144,400;9..144,500"
+ "&family=Inter:wght@400;500"
+ "&family=JetBrains+Mono:wght@400;500"
+)
+CSS_URL = f"https://fonts.googleapis.com/css2?{FAMILIES}&display=swap"
+
+# A modern browser UA is required, not cosmetic: Google serves ancient TTF to
+# unrecognised clients and woff2 only to browsers known to support it.
+UA = ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
+ "(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36")
+
+
+def fetch(url, timeout=30):
+ req = urllib.request.Request(url, headers={"User-Agent": UA})
+ with urllib.request.urlopen(req, timeout=timeout) as r:
+ return r.read()
+
+
+def main():
+ css = fetch(CSS_URL).decode()
+
+ # Google emits a comment naming each subset before its @font-face block.
+ blocks = re.split(r"(?=/\*\s*[a-z0-9-]+\s*\*/)", css)
+ OUT.mkdir(parents=True, exist_ok=True)
+ for old in OUT.glob("*.woff2"):
+ old.unlink()
+
+ out_css, seen, total = [], {}, 0
+ for block in blocks:
+ subset = re.search(r"/\*\s*([a-z0-9-]+)\s*\*/", block)
+ family = re.search(r"font-family:\s*'([^']+)'", block)
+ weight = re.search(r"font-weight:\s*([^;]+);", block)
+ url = re.search(r"url\((https://[^)]+\.woff2)\)", block)
+ if not (subset and family and url):
+ continue
+ w = weight.group(1).strip().replace(" ", "-") if weight else "400"
+ name = f"{family.group(1).replace(' ', '')}-{w}-{subset.group(1)}.woff2"
+ if name not in seen:
+ data = fetch(url.group(1))
+ (OUT / name).write_bytes(data)
+ seen[name] = len(data)
+ total += len(data)
+ out_css.append(block.replace(url.group(1), f"./{name}"))
+
+ header = (
+ "/* Generated by tools/vendor-fonts.py — do not edit by hand.\n"
+ " *\n"
+ " * Vendored from Google Fonts and served from our own origin: the app\n"
+ " * has to render with no outbound network, so a font provider link is\n"
+ " * not an option. Urls are relative so this keeps working under a base\n"
+ " * path. unicode-range is preserved, so the browser still fetches only\n"
+ " * the subsets a page needs.\n"
+ " */\n"
+ )
+ (OUT / "fonts.css").write_text(header + "".join(out_css))
+ for n, size in sorted(seen.items()):
+ print(f" {n:<44} {size/1024:7.1f} KB")
+ print(f"{len(seen)} files, {total/1024/1024:.2f} MB total -> {OUT.relative_to(ROOT)}")
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/web/src/app.html b/web/src/app.html
index 6e7479b6..1a1de560 100644
--- a/web/src/app.html
+++ b/web/src/app.html
@@ -30,12 +30,12 @@
}
})();
-
-
-
+
+
%sveltekit.head%
diff --git a/web/src/lib/no-external-assets.test.ts b/web/src/lib/no-external-assets.test.ts
new file mode 100644
index 00000000..9cb71f54
--- /dev/null
+++ b/web/src/lib/no-external-assets.test.ts
@@ -0,0 +1,80 @@
+import { readFileSync, readdirSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { describe, expect, test } from 'vitest';
+
+// A deployed instance has no outbound network, so every asset the app draws
+// itself with has to come from our own origin. This regressed once already
+// (the font stylesheet was linked straight from Google), and the failure is
+// invisible in development — the dev machine HAS internet, so the fonts load
+// fine and only a real deployment renders in fallback faces.
+//
+// The assertion is on the PROPERTY, not on a vendor: any absolute URL in a
+// resource-loading attribute fails, whoever is hosting it. Naming
+// `fonts.googleapis.com` would pass the day someone reached for a different
+// CDN, which is the same bug.
+
+// fileURLToPath rather than import.meta.dirname: the latter needs Node 20.11+
+// and this repo's toolchain is still on 18, where it is silently undefined.
+const WEB = join(dirname(fileURLToPath(import.meta.url)), '../..');
+
+/** Strip comments before asserting an ABSENCE — otherwise prose describing
+ * the forbidden thing satisfies the check that forbids it. */
+const stripComments = (html: string) => html.replace(//g, '');
+
+/** href/src on anything that makes the browser fetch a subresource. */
+const RESOURCE_URL = /<(?:link|script|img|source|iframe)\b[^>]*?\b(?:href|src)\s*=\s*["']([^"']+)["']/gi;
+
+const externalUrlsIn = (html: string) =>
+ [...stripComments(html).matchAll(RESOURCE_URL)]
+ .map((m) => m[1])
+ .filter((u) => /^(?:https?:)?\/\//i.test(u));
+
+describe('the app ships every asset it draws itself with', () => {
+ test('app.html loads no subresource from a third-party origin', () => {
+ const html = readFileSync(join(WEB, 'src/app.html'), 'utf8');
+ expect(externalUrlsIn(html)).toEqual([]);
+ });
+
+ test('app.html does not preconnect or dns-prefetch to any host', () => {
+ // preconnect carries no href-fetch of its own, so the check above misses
+ // it — but it is the fingerprint of a third-party asset about to be added.
+ const html = stripComments(readFileSync(join(WEB, 'src/app.html'), 'utf8'));
+ const hints = [...html.matchAll(/]*\brel\s*=\s*["']([^"']+)["'][^>]*>/gi)]
+ .filter((m) => /\b(?:preconnect|dns-prefetch)\b/i.test(m[1]));
+ expect(hints.map((m) => m[0])).toEqual([]);
+ });
+
+ test('the vendored font stylesheet exists and points only at local files', () => {
+ const dir = join(WEB, 'static/fonts');
+ const css = readFileSync(join(dir, 'fonts.css'), 'utf8');
+
+ const urls = [...css.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/gi)].map((m) => m[1]);
+ expect(urls.length).toBeGreaterThan(0);
+ expect(urls.filter((u) => /^(?:https?:)?\/\//i.test(u))).toEqual([]);
+
+ // Every referenced file is actually present — a stylesheet pointing at a
+ // woff2 nobody committed fails exactly like a CDN link would, offline.
+ const present = new Set(readdirSync(dir));
+ const missing = urls
+ .map((u) => u.replace(/^\.\//, ''))
+ .filter((f) => !present.has(f));
+ expect(missing).toEqual([]);
+ });
+
+ test('every font family the tokens name is actually vendored', () => {
+ // Pins the families to the TOKENS rather than to a hardcoded list, so
+ // swapping a typeface in tokens.json fails here until it is vendored.
+ const tokens = JSON.parse(
+ readFileSync(join(WEB, 'src/lib/styles/tokens.json'), 'utf8')
+ ) as { fonts: Record };
+ const files = readdirSync(join(WEB, 'static/fonts'));
+ for (const family of Object.values(tokens.fonts)) {
+ const prefix = `${family.replace(/\s+/g, '')}-`;
+ expect(
+ files.some((f) => f.startsWith(prefix) && f.endsWith('.woff2')),
+ `no vendored woff2 for "${family}"`
+ ).toBe(true);
+ }
+ });
+});
diff --git a/web/static/fonts/Fraunces-400-latin-ext.woff2 b/web/static/fonts/Fraunces-400-latin-ext.woff2
new file mode 100644
index 00000000..b6e3e3a5
Binary files /dev/null and b/web/static/fonts/Fraunces-400-latin-ext.woff2 differ
diff --git a/web/static/fonts/Fraunces-400-latin.woff2 b/web/static/fonts/Fraunces-400-latin.woff2
new file mode 100644
index 00000000..cb295bf0
Binary files /dev/null and b/web/static/fonts/Fraunces-400-latin.woff2 differ
diff --git a/web/static/fonts/Fraunces-400-vietnamese.woff2 b/web/static/fonts/Fraunces-400-vietnamese.woff2
new file mode 100644
index 00000000..43126c27
Binary files /dev/null and b/web/static/fonts/Fraunces-400-vietnamese.woff2 differ
diff --git a/web/static/fonts/Fraunces-500-latin-ext.woff2 b/web/static/fonts/Fraunces-500-latin-ext.woff2
new file mode 100644
index 00000000..b6e3e3a5
Binary files /dev/null and b/web/static/fonts/Fraunces-500-latin-ext.woff2 differ
diff --git a/web/static/fonts/Fraunces-500-latin.woff2 b/web/static/fonts/Fraunces-500-latin.woff2
new file mode 100644
index 00000000..cb295bf0
Binary files /dev/null and b/web/static/fonts/Fraunces-500-latin.woff2 differ
diff --git a/web/static/fonts/Fraunces-500-vietnamese.woff2 b/web/static/fonts/Fraunces-500-vietnamese.woff2
new file mode 100644
index 00000000..43126c27
Binary files /dev/null and b/web/static/fonts/Fraunces-500-vietnamese.woff2 differ
diff --git a/web/static/fonts/Inter-400-cyrillic-ext.woff2 b/web/static/fonts/Inter-400-cyrillic-ext.woff2
new file mode 100644
index 00000000..de83a9c7
Binary files /dev/null and b/web/static/fonts/Inter-400-cyrillic-ext.woff2 differ
diff --git a/web/static/fonts/Inter-400-cyrillic.woff2 b/web/static/fonts/Inter-400-cyrillic.woff2
new file mode 100644
index 00000000..d7509147
Binary files /dev/null and b/web/static/fonts/Inter-400-cyrillic.woff2 differ
diff --git a/web/static/fonts/Inter-400-greek-ext.woff2 b/web/static/fonts/Inter-400-greek-ext.woff2
new file mode 100644
index 00000000..6e7141f8
Binary files /dev/null and b/web/static/fonts/Inter-400-greek-ext.woff2 differ
diff --git a/web/static/fonts/Inter-400-greek.woff2 b/web/static/fonts/Inter-400-greek.woff2
new file mode 100644
index 00000000..024f0770
Binary files /dev/null and b/web/static/fonts/Inter-400-greek.woff2 differ
diff --git a/web/static/fonts/Inter-400-latin-ext.woff2 b/web/static/fonts/Inter-400-latin-ext.woff2
new file mode 100644
index 00000000..479d010d
Binary files /dev/null and b/web/static/fonts/Inter-400-latin-ext.woff2 differ
diff --git a/web/static/fonts/Inter-400-latin.woff2 b/web/static/fonts/Inter-400-latin.woff2
new file mode 100644
index 00000000..d15208de
Binary files /dev/null and b/web/static/fonts/Inter-400-latin.woff2 differ
diff --git a/web/static/fonts/Inter-400-vietnamese.woff2 b/web/static/fonts/Inter-400-vietnamese.woff2
new file mode 100644
index 00000000..a40c4699
Binary files /dev/null and b/web/static/fonts/Inter-400-vietnamese.woff2 differ
diff --git a/web/static/fonts/Inter-500-cyrillic-ext.woff2 b/web/static/fonts/Inter-500-cyrillic-ext.woff2
new file mode 100644
index 00000000..de83a9c7
Binary files /dev/null and b/web/static/fonts/Inter-500-cyrillic-ext.woff2 differ
diff --git a/web/static/fonts/Inter-500-cyrillic.woff2 b/web/static/fonts/Inter-500-cyrillic.woff2
new file mode 100644
index 00000000..d7509147
Binary files /dev/null and b/web/static/fonts/Inter-500-cyrillic.woff2 differ
diff --git a/web/static/fonts/Inter-500-greek-ext.woff2 b/web/static/fonts/Inter-500-greek-ext.woff2
new file mode 100644
index 00000000..6e7141f8
Binary files /dev/null and b/web/static/fonts/Inter-500-greek-ext.woff2 differ
diff --git a/web/static/fonts/Inter-500-greek.woff2 b/web/static/fonts/Inter-500-greek.woff2
new file mode 100644
index 00000000..024f0770
Binary files /dev/null and b/web/static/fonts/Inter-500-greek.woff2 differ
diff --git a/web/static/fonts/Inter-500-latin-ext.woff2 b/web/static/fonts/Inter-500-latin-ext.woff2
new file mode 100644
index 00000000..479d010d
Binary files /dev/null and b/web/static/fonts/Inter-500-latin-ext.woff2 differ
diff --git a/web/static/fonts/Inter-500-latin.woff2 b/web/static/fonts/Inter-500-latin.woff2
new file mode 100644
index 00000000..d15208de
Binary files /dev/null and b/web/static/fonts/Inter-500-latin.woff2 differ
diff --git a/web/static/fonts/Inter-500-vietnamese.woff2 b/web/static/fonts/Inter-500-vietnamese.woff2
new file mode 100644
index 00000000..a40c4699
Binary files /dev/null and b/web/static/fonts/Inter-500-vietnamese.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-400-cyrillic-ext.woff2 b/web/static/fonts/JetBrainsMono-400-cyrillic-ext.woff2
new file mode 100644
index 00000000..289de931
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-400-cyrillic-ext.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-400-cyrillic.woff2 b/web/static/fonts/JetBrainsMono-400-cyrillic.woff2
new file mode 100644
index 00000000..de39acbc
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-400-cyrillic.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-400-greek.woff2 b/web/static/fonts/JetBrainsMono-400-greek.woff2
new file mode 100644
index 00000000..a53d52d6
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-400-greek.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-400-latin-ext.woff2 b/web/static/fonts/JetBrainsMono-400-latin-ext.woff2
new file mode 100644
index 00000000..82f96681
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-400-latin-ext.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-400-latin.woff2 b/web/static/fonts/JetBrainsMono-400-latin.woff2
new file mode 100644
index 00000000..4d09cda4
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-400-latin.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-400-vietnamese.woff2 b/web/static/fonts/JetBrainsMono-400-vietnamese.woff2
new file mode 100644
index 00000000..e38f5538
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-400-vietnamese.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-500-cyrillic-ext.woff2 b/web/static/fonts/JetBrainsMono-500-cyrillic-ext.woff2
new file mode 100644
index 00000000..289de931
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-500-cyrillic-ext.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-500-cyrillic.woff2 b/web/static/fonts/JetBrainsMono-500-cyrillic.woff2
new file mode 100644
index 00000000..de39acbc
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-500-cyrillic.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-500-greek.woff2 b/web/static/fonts/JetBrainsMono-500-greek.woff2
new file mode 100644
index 00000000..a53d52d6
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-500-greek.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-500-latin-ext.woff2 b/web/static/fonts/JetBrainsMono-500-latin-ext.woff2
new file mode 100644
index 00000000..82f96681
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-500-latin-ext.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-500-latin.woff2 b/web/static/fonts/JetBrainsMono-500-latin.woff2
new file mode 100644
index 00000000..4d09cda4
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-500-latin.woff2 differ
diff --git a/web/static/fonts/JetBrainsMono-500-vietnamese.woff2 b/web/static/fonts/JetBrainsMono-500-vietnamese.woff2
new file mode 100644
index 00000000..e38f5538
Binary files /dev/null and b/web/static/fonts/JetBrainsMono-500-vietnamese.woff2 differ
diff --git a/web/static/fonts/fonts.css b/web/static/fonts/fonts.css
new file mode 100644
index 00000000..172a244d
--- /dev/null
+++ b/web/static/fonts/fonts.css
@@ -0,0 +1,296 @@
+/* Generated by tools/vendor-fonts.py — do not edit by hand.
+ *
+ * Vendored from Google Fonts and served from our own origin: the app
+ * has to render with no outbound network, so a font provider link is
+ * not an option. Urls are relative so this keeps working under a base
+ * path. unicode-range is preserved, so the browser still fetches only
+ * the subsets a page needs.
+ */
+/* vietnamese */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./Fraunces-400-vietnamese.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, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./Fraunces-400-latin-ext.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, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./Fraunces-400-latin.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, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./Fraunces-500-vietnamese.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, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./Fraunces-500-latin-ext.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, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./Fraunces-500-latin.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, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./Inter-400-cyrillic-ext.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./Inter-400-cyrillic.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./Inter-400-greek-ext.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./Inter-400-greek.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./Inter-400-vietnamese.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, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./Inter-400-latin-ext.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, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./Inter-400-latin.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, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./Inter-500-cyrillic-ext.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./Inter-500-cyrillic.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./Inter-500-greek-ext.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./Inter-500-greek.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./Inter-500-vietnamese.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, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./Inter-500-latin-ext.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, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./Inter-500-latin.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, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./JetBrainsMono-400-cyrillic-ext.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./JetBrainsMono-400-cyrillic.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./JetBrainsMono-400-greek.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./JetBrainsMono-400-vietnamese.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, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./JetBrainsMono-400-latin-ext.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, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(./JetBrainsMono-400-latin.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, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./JetBrainsMono-500-cyrillic-ext.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./JetBrainsMono-500-cyrillic.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./JetBrainsMono-500-greek.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./JetBrainsMono-500-vietnamese.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, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./JetBrainsMono-500-latin-ext.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, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url(./JetBrainsMono-500-latin.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, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}