diff --git a/build.rs b/build.rs index d64b9c5..2687d71 100644 --- a/build.rs +++ b/build.rs @@ -69,6 +69,7 @@ fn build_webui(out_dir: &Path) { "webui/package-lock.json", "webui/svelte.config.js", "webui/vite.config.js", + "webui/public", ] { println!("cargo:rerun-if-changed={path}"); } diff --git a/src/gateway/http.rs b/src/gateway/http.rs index 34a6727..2d5bb40 100644 --- a/src/gateway/http.rs +++ b/src/gateway/http.rs @@ -78,6 +78,41 @@ pub async fn webui_styles() -> Response { ) } +const EMBEDDED_FONTS: &[(&str, &[u8])] = &[ + ( + "space-grotesk-500.woff2", + include_bytes!(concat!(env!("OUT_DIR"), "/webui/fonts/space-grotesk-500.woff2")), + ), + ( + "space-grotesk-700.woff2", + include_bytes!(concat!(env!("OUT_DIR"), "/webui/fonts/space-grotesk-700.woff2")), + ), + ( + "jetbrains-mono-400.woff2", + include_bytes!(concat!(env!("OUT_DIR"), "/webui/fonts/jetbrains-mono-400.woff2")), + ), + ( + "jetbrains-mono-700.woff2", + include_bytes!(concat!(env!("OUT_DIR"), "/webui/fonts/jetbrains-mono-700.woff2")), + ), +]; + +pub async fn webui_font(Path(name): Path) -> Response { + let bytes = EMBEDDED_FONTS + .iter() + .find(|(font_name, _)| *font_name == name) + .map(|(_, bytes)| *bytes); + let Some(bytes) = bytes else { + return StatusCode::NOT_FOUND.into_response(); + }; + Response::builder() + .header(header::CONTENT_TYPE, "font/woff2") + .header(header::CACHE_CONTROL, "public, max-age=31536000, immutable") + .header("X-Content-Type-Options", "nosniff") + .body(Body::from(bytes)) + .expect("valid font response") +} + #[derive(Debug)] pub struct ApiError { status: StatusCode, diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 71bb193..78ede72 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -593,6 +593,7 @@ fn build_router(state: Arc) -> Router { .route("/", routing::get(http::webui_index)) .route("/app.js", routing::get(http::webui_script)) .route("/styles.css", routing::get(http::webui_styles)) + .route("/fonts/{name}", routing::get(http::webui_font)) .route("/health", routing::get(http::health)) .route("/api/auth/status", routing::get(auth::status)) .route("/api/auth/pair", routing::post(auth::pair)) diff --git a/webui/package-lock.json b/webui/package-lock.json index e8dd7ea..1e26fce 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -14,6 +14,8 @@ "svelte": "^5.0.0" }, "devDependencies": { + "@fontsource/jetbrains-mono": "^5.3.0", + "@fontsource/space-grotesk": "^5.3.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", "@types/node": "^24.0.0", "svelte-check": "^4.0.0", @@ -490,6 +492,26 @@ "resolved": "https://mirrors.cloud.tencent.com/npm/@floating-ui/utils/-/utils-0.2.12.tgz", "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==" }, + "node_modules/@fontsource/jetbrains-mono": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/@fontsource/jetbrains-mono/-/jetbrains-mono-5.3.0.tgz", + "integrity": "sha512-fqDfB5I9f1p1TV486aUgB9t8zP84P0O1FtQR5Ol9vjwPy+S+EIGlVYm1cvj2W5shcZMTg2nZFdVMoH5wFu8a1A==", + "dev": true, + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/space-grotesk": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/@fontsource/space-grotesk/-/space-grotesk-5.3.0.tgz", + "integrity": "sha512-ksnGizDPXIDuvqcTYTSrmZ+evx9sDlS8rp7+42BQ7wU+spt3twEoXfbJz672C+5CLg6VeUQwRy5RXshWb67LcQ==", + "dev": true, + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@internationalized/date": { "version": "3.12.2", "resolved": "https://mirrors.cloud.tencent.com/npm/@internationalized/date/-/date-3.12.2.tgz", diff --git a/webui/package.json b/webui/package.json index 7616b81..5836e90 100644 --- a/webui/package.json +++ b/webui/package.json @@ -18,6 +18,8 @@ "svelte": "^5.0.0" }, "devDependencies": { + "@fontsource/jetbrains-mono": "^5.3.0", + "@fontsource/space-grotesk": "^5.3.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", "@types/node": "^24.0.0", "svelte-check": "^4.0.0", diff --git a/webui/public/fonts/jetbrains-mono-400.woff2 b/webui/public/fonts/jetbrains-mono-400.woff2 new file mode 100644 index 0000000..5858873 Binary files /dev/null and b/webui/public/fonts/jetbrains-mono-400.woff2 differ diff --git a/webui/public/fonts/jetbrains-mono-700.woff2 b/webui/public/fonts/jetbrains-mono-700.woff2 new file mode 100644 index 0000000..3a4e333 Binary files /dev/null and b/webui/public/fonts/jetbrains-mono-700.woff2 differ diff --git a/webui/public/fonts/space-grotesk-500.woff2 b/webui/public/fonts/space-grotesk-500.woff2 new file mode 100644 index 0000000..0db251f Binary files /dev/null and b/webui/public/fonts/space-grotesk-500.woff2 differ diff --git a/webui/public/fonts/space-grotesk-700.woff2 b/webui/public/fonts/space-grotesk-700.woff2 new file mode 100644 index 0000000..44604a0 Binary files /dev/null and b/webui/public/fonts/space-grotesk-700.woff2 differ