diff --git a/app/routes/_marketing+/index.tsx b/app/routes/_marketing+/index.tsx
index ebbb7a9d4..30c987950 100644
--- a/app/routes/_marketing+/index.tsx
+++ b/app/routes/_marketing+/index.tsx
@@ -50,7 +50,7 @@ export default function Index() {
diff --git a/app/styles/tailwind.css b/app/styles/tailwind.css
index d52030242..8056abc17 100644
--- a/app/styles/tailwind.css
+++ b/app/styles/tailwind.css
@@ -3,6 +3,10 @@
@custom-variant dark (&:is(.dark *));
+.font-title {
+ font-family: 'Poppins', sans-serif;
+}
+
:root {
--radius: 0.5rem;
diff --git a/docs/fonts.md b/docs/fonts.md
index a069f37fa..38d39ee89 100644
--- a/docs/fonts.md
+++ b/docs/fonts.md
@@ -6,147 +6,37 @@ easy to do so.
## Using Custom Fonts
-You can use custom fonts by creating the `./public/fonts` directory (if it
-doesn't already exist) and adding them to it.
-[Google Fonts](https://fonts.google.com/) is a good place to find open source
-fonts. You will also need to add the `css` file for the font to the
-`./app/styles` directory, if your font doesn't come with one (Google Fonts
-don't) you can generate one using a tool like
-[Transfonter](https://transfonter.org/). Transfonter now has a fonts directory
-setting. Set that to `fonts` to have the `url` preset.
+For custom fonts, Epic Stack uses
+[`fontless`](https://github.com/unjs/fontaine/tree/main/packages/fontless) for
+automatic font optimization. This provides zero-runtime font loading with proper
+fallbacks to reduce Cumulative Layout Shift (CLS).
-Verify the `url` in the `css` is relative to the `public` folder. So it should
-look something like `url('/fonts/yourfont/yourfont-200.woff2')`.
+## How it works
-Now you've added your font, there's a few places you need to update to use it.
+Simply use font families in your CSS and `fontless` handles the rest:
-1. Add your font to the CSS variables.
+```css
+.font-title {
+ font-family: 'Poppins', sans-serif;
+}
- ```css
- /* tailwind.css */
- @layer base {
- :root {
- --font-sans: ;
- }
- }
- ```
+.body-text {
+ font-family: 'Inter', sans-serif;
+}
+```
-2. Import the default theme and add your font to the `fontFamily` property.
+The plugin will:
- ```ts
- import defaultTheme from 'tailwindcss/defaultTheme.js'
- // tailwind.config.ts
- extend: {
- ...extendedTheme,
- fontFamily: {
- sans: ['var(--font-sans)', ...defaultTheme.fontFamily.sans],
- }
- }
+- Detect font-family declarations in your CSS
+- Resolve fonts from providers (Google Fonts, Bunny Fonts, etc.)
+- Generate optimized `@font-face` declarations
+- Add metric-based fallback fonts to reduce CLS
+- Download and serve font assets from `/_fonts/`
- ```
+## Troubleshooting
-3. Import your font stylesheet.
+### Fonts not loading in development
- ```tsx
- // app/routes/root.tsx
- import fontStyleSheetUrl from './styles/yourfont.css?url'
- ```
-
- Add the font stylesheet to the links array.
-
- ```tsx
- // app/routes/root.tsx
- ...
- { rel: 'preload', href: fontStyleSheetUrl, as: 'style' },
- { rel: 'stylesheet', href: fontStyleSheetUrl },
- ```
-
-4. Expose and cache your fonts folder.
-
- ```ts
- // server/index.ts
- ...
- app.use(
- '/fonts',
- // Can aggressively cache fonts as they don't change often
- express.static('public/fonts', { immutable: true, maxAge: '1y' }),
- )
- ```
-
-That's it! You can now use your custom font should now be available to use in
-your site.
-
-## Font Metric Overrides
-
-When using custom fonts, your site elements may stretch or shrink to accommodate
-the font. This is because the browser doesn't know the dimensions of the font
-you're using until it arrives, which introduces Cumulative Layout Shift and
-impact its web vitals.
-
-In Epic Stack, we fixed this by introducing
-[Font Metric Overrides](https://github.com/epicweb-dev/epic-stack/pull/128/files).
-
-Follow the steps below to add Font Metric Overrides to your custom fonts.
-
-1. Generate the overrides for your font.
-
- You can use [fontpie](https://www.npmjs.com/package/fontpie) utility to
- generate the overrides. For each of your fonts, write the following in your
- terminal:
-
- ```bash
- npx fontpie ./local/font/location.woff2 -w font-weight -s normal/italic -n YourFont
- ```
-
- #### Example
-
- ```sh
- npx fontpie ./public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-200.woff2 -w 200 -s normal -n NunitoSans
- ```
-
- ```css
- @font-face {
- font-family: 'NunitoSans Fallback';
- font-style: normal;
- font-weight: 200;
- src: local('Arial');
- ascent-override: 103.02%;
- descent-override: 35.97%;
- line-gap-override: 0%;
- size-adjust: 98.13%;
- }
- ```
-
- If you've got a lot of font files to override, you can use
- [fontpie-from-css](https://github.com/matt-kinton/fontpie-from-css) to
- generate the overrides from a CSS file.
-
- ```sh
- npx fontpie-from-css ./public/fonts/yourfont/yourfont.css
- ```
-
- **_Note:_** _If you've been following the steps above, you might have to copy
- your `yourfont.css` file temporarily to the `./public` folder as
- `fontpie-from-css` loads fonts relative to the CSS file._
-
-2. Add the overrides to your font stylesheet.
-
- Use fontpie for every custom font used (including variants) or
- fontpie-from-css and add the metric overrides to `yourfont.css`.
-
- _Ensure the original font has the `font-display: swap` property or the
- fallback wouldn't work!_
-
-3. Add the font fallback to the stylesheet.
-
- ```css
- /* tailwind.css */
- @layer base {
- :root {
- --font-sans: ;
- }
- }
- ```
-
-That's it! You can now use your custom font without worrying about Cumulative
-Layout Shift!
+This is expected behavior. Fonts are only generated in the build files. To see
+fonts in development mode, build the application first and then run
+`npm run dev`.
diff --git a/package-lock.json b/package-lock.json
index 02c417d12..2d481f541 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -53,6 +53,7 @@
"execa": "^9.5.2",
"express": "^4.21.2",
"express-rate-limit": "^7.5.0",
+ "fontless": "^0.0.2",
"get-port": "^7.1.0",
"glob": "^11.0.2",
"input-otp": "^1.4.2",
@@ -786,6 +787,21 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/@capsizecss/metrics": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@capsizecss/metrics/-/metrics-3.5.0.tgz",
+ "integrity": "sha512-Ju2I/Qn3c1OaU8FgeW4Tc22D4C9NwyVfKzNmzst59bvxBjPoLYNZMqFYn+HvCtn4MpXwiaDtCE8fNuQLpdi9yA=="
+ },
+ "node_modules/@capsizecss/unpack": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-2.4.0.tgz",
+ "integrity": "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==",
+ "dependencies": {
+ "blob-to-buffer": "^1.2.8",
+ "cross-fetch": "^3.0.4",
+ "fontkit": "^2.0.2"
+ }
+ },
"node_modules/@conform-to/dom": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@conform-to/dom/-/dom-1.7.2.tgz",
@@ -1183,7 +1199,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1200,7 +1215,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1217,7 +1231,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1234,7 +1247,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1251,7 +1263,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1268,7 +1279,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1285,7 +1295,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1302,7 +1311,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1319,7 +1327,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1336,7 +1343,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1353,7 +1359,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1370,7 +1375,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1387,7 +1391,6 @@
"cpu": [
"mips64el"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1404,7 +1407,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1421,7 +1423,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1438,7 +1439,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1455,7 +1455,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1472,7 +1471,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1489,7 +1487,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1506,7 +1503,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1523,7 +1519,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1540,7 +1535,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1557,7 +1551,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1574,7 +1567,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1591,7 +1583,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2528,6 +2519,15 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -2547,10 +2547,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "license": "MIT"
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
@@ -6081,6 +6080,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@swc/helpers": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
+ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
"node_modules/@tailwindcss/node": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
@@ -6607,7 +6614,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "dev": true,
"license": "MIT"
},
"node_modules/@types/express": {
@@ -8257,7 +8263,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -8378,6 +8383,25 @@
"node": ">= 6"
}
},
+ "node_modules/blob-to-buffer": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/blob-to-buffer/-/blob-to-buffer-1.2.9.tgz",
+ "integrity": "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@@ -8445,6 +8469,14 @@
"node": ">=8"
}
},
+ "node_modules/brotli": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
+ "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
+ "dependencies": {
+ "base64-js": "^1.1.2"
+ }
+ },
"node_modules/browserslist": {
"version": "4.25.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
@@ -8669,7 +8701,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
@@ -8826,7 +8857,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.8"
@@ -8996,6 +9026,11 @@
"typedarray": "^0.0.6"
}
},
+ "node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="
+ },
"node_modules/config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
@@ -9007,6 +9042,14 @@
"proto-list": "~1.2.1"
}
},
+ "node_modules/consola": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
+ "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -9043,6 +9086,11 @@
"node": ">=18"
}
},
+ "node_modules/cookie-es": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz",
+ "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="
+ },
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -9101,6 +9149,14 @@
"yarn": ">=1"
}
},
+ "node_modules/cross-fetch": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
+ "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
+ "dependencies": {
+ "node-fetch": "^2.7.0"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -9130,6 +9186,14 @@
"node": ">= 8"
}
},
+ "node_modules/crossws": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz",
+ "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==",
+ "dependencies": {
+ "uncrypto": "^0.1.3"
+ }
+ },
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
@@ -9147,6 +9211,18 @@
"url": "https://github.com/sponsors/fb55"
}
},
+ "node_modules/css-tree": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "dependencies": {
+ "mdn-data": "2.12.2",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
@@ -9556,6 +9632,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/defu": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
+ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
+ },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -9585,6 +9666,11 @@
"node": ">=6"
}
},
+ "node_modules/destr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
+ "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="
+ },
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@@ -9610,6 +9696,11 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
+ "node_modules/dfa": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
+ "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="
+ },
"node_modules/diff": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
@@ -10056,7 +10147,6 @@
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -10563,7 +10653,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
@@ -10726,7 +10815,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
"license": "MIT"
},
"node_modules/fast-glob": {
@@ -10915,6 +11003,115 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/fontaine": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/fontaine/-/fontaine-0.6.0.tgz",
+ "integrity": "sha512-cfKqzB62GmztJhwJ0YXtzNsmpqKAcFzTqsakJ//5COTzbou90LU7So18U+4D8z+lDXr4uztaAUZBonSoPDcj1w==",
+ "dependencies": {
+ "@capsizecss/metrics": "^3.5.0",
+ "@capsizecss/unpack": "^2.4.0",
+ "css-tree": "^3.1.0",
+ "magic-regexp": "^0.10.0",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "ufo": "^1.6.1",
+ "unplugin": "^2.3.2"
+ }
+ },
+ "node_modules/fontaine/node_modules/magic-string": {
+ "version": "0.30.18",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz",
+ "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/fontaine/node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="
+ },
+ "node_modules/fontaine/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/fontaine/node_modules/unplugin": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.9.tgz",
+ "integrity": "sha512-2dcbZq6aprwXTkzptq3k5qm5B8cvpjG9ynPd5fyM2wDJuuF7PeUK64Sxf0d+X1ZyDOeGydbNzMqBSIVlH8GIfA==",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "acorn": "^8.15.0",
+ "picomatch": "^4.0.3",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/fontaine/node_modules/webpack-virtual-modules": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="
+ },
+ "node_modules/fontkit": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
+ "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
+ "dependencies": {
+ "@swc/helpers": "^0.5.12",
+ "brotli": "^1.3.2",
+ "clone": "^2.1.2",
+ "dfa": "^1.2.0",
+ "fast-deep-equal": "^3.1.3",
+ "restructure": "^3.0.0",
+ "tiny-inflate": "^1.0.3",
+ "unicode-properties": "^1.4.0",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/fontless": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/fontless/-/fontless-0.0.2.tgz",
+ "integrity": "sha512-duDdbyLChMc7YGnBsO25kwqjWJOxr0Noy3NHJeYkQCUA4MlL+lp4eeNx4X4OLLOpkCVUMu1fN2IZuoGjJJAhXg==",
+ "dependencies": {
+ "consola": "^3.4.2",
+ "css-tree": "^3.1.0",
+ "defu": "^6.1.4",
+ "esbuild": "^0.25.4",
+ "fontaine": "0.6.0",
+ "jiti": "^2.4.2",
+ "magic-string": "^0.30.17",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "ufo": "^1.6.1",
+ "unifont": "^0.5.0",
+ "unstorage": "^1.16.0"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/fontless/node_modules/magic-string": {
+ "version": "0.30.18",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz",
+ "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/fontless/node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="
+ },
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -11353,6 +11550,22 @@
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
+ "node_modules/h3": {
+ "version": "1.15.4",
+ "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz",
+ "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==",
+ "dependencies": {
+ "cookie-es": "^1.2.2",
+ "crossws": "^0.3.5",
+ "defu": "^6.1.4",
+ "destr": "^2.0.5",
+ "iron-webcrypto": "^1.2.1",
+ "node-mock-http": "^1.0.2",
+ "radix3": "^1.1.2",
+ "ufo": "^1.6.1",
+ "uncrypto": "^0.1.3"
+ }
+ },
"node_modules/has-bigints": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -11778,6 +11991,14 @@
"node": ">= 0.10"
}
},
+ "node_modules/iron-webcrypto": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
+ "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==",
+ "funding": {
+ "url": "https://github.com/sponsors/brc-dd"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -13098,6 +13319,58 @@
"lz-string": "bin/bin.js"
}
},
+ "node_modules/magic-regexp": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.10.0.tgz",
+ "integrity": "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==",
+ "dependencies": {
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.12",
+ "mlly": "^1.7.2",
+ "regexp-tree": "^0.1.27",
+ "type-level-regexp": "~0.1.17",
+ "ufo": "^1.5.4",
+ "unplugin": "^2.0.0"
+ }
+ },
+ "node_modules/magic-regexp/node_modules/magic-string": {
+ "version": "0.30.18",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz",
+ "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/magic-regexp/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/magic-regexp/node_modules/unplugin": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.9.tgz",
+ "integrity": "sha512-2dcbZq6aprwXTkzptq3k5qm5B8cvpjG9ynPd5fyM2wDJuuF7PeUK64Sxf0d+X1ZyDOeGydbNzMqBSIVlH8GIfA==",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "acorn": "^8.15.0",
+ "picomatch": "^4.0.3",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/magic-regexp/node_modules/webpack-virtual-modules": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="
+ },
"node_modules/magic-string": {
"version": "0.30.8",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
@@ -13171,6 +13444,11 @@
"react": "^18.0 || ^19.0"
}
},
+ "node_modules/mdn-data": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="
+ },
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -13348,6 +13626,22 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/mlly": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
+ "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "pathe": "^2.0.3",
+ "pkg-types": "^1.3.1",
+ "ufo": "^1.6.1"
+ }
+ },
+ "node_modules/mlly/node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="
+ },
"node_modules/module-details-from-path": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz",
@@ -13609,6 +13903,11 @@
}
}
},
+ "node_modules/node-fetch-native": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
+ "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="
+ },
"node_modules/node-fetch/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -13642,6 +13941,11 @@
"he": "1.2.0"
}
},
+ "node_modules/node-mock-http": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.2.tgz",
+ "integrity": "sha512-zWaamgDUdo9SSLw47we78+zYw/bDr5gH8pH7oRRs8V3KmBtu8GLgGIbV2p/gRPd3LWpEOpjQj7X1FOU3VFMJ8g=="
+ },
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -14114,6 +14418,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/ofetch": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz",
+ "integrity": "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==",
+ "dependencies": {
+ "destr": "^2.0.3",
+ "node-fetch-native": "^1.6.4",
+ "ufo": "^1.5.4"
+ }
+ },
+ "node_modules/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="
+ },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -14302,6 +14621,11 @@
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
+ "node_modules/pako": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -14566,6 +14890,21 @@
"node": ">=4"
}
},
+ "node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/pkg-types/node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="
+ },
"node_modules/playwright": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz",
@@ -15244,6 +15583,11 @@
],
"license": "MIT"
},
+ "node_modules/radix3": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz",
+ "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="
+ },
"node_modules/railroad-diagrams": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
@@ -15671,7 +16015,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
@@ -15718,6 +16061,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/regexp-tree": {
+ "version": "0.1.27",
+ "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
+ "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==",
+ "bin": {
+ "regexp-tree": "bin/regexp-tree"
+ }
+ },
"node_modules/regexp.prototype.flags": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@@ -15997,6 +16348,11 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/restructure": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
+ "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="
+ },
"node_modules/ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
@@ -17263,6 +17619,11 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/tiny-inflate": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
+ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
+ },
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -17542,6 +17903,11 @@
"node": ">= 0.6"
}
},
+ "node_modules/type-level-regexp": {
+ "version": "0.1.17",
+ "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.17.tgz",
+ "integrity": "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg=="
+ },
"node_modules/typed-array-buffer": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
@@ -17664,6 +18030,11 @@
"typescript": ">=4.8.4 <5.9.0"
}
},
+ "node_modules/ufo": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
+ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="
+ },
"node_modules/unbox-primitive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
@@ -17683,12 +18054,35 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/uncrypto": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
+ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
+ },
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
+ "node_modules/unicode-properties": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
+ "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
+ "dependencies": {
+ "base64-js": "^1.3.0",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/unicode-trie": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
+ "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
+ "dependencies": {
+ "pako": "^0.2.5",
+ "tiny-inflate": "^1.0.0"
+ }
+ },
"node_modules/unicorn-magic": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
@@ -17701,6 +18095,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/unifont": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.5.2.tgz",
+ "integrity": "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg==",
+ "dependencies": {
+ "css-tree": "^3.0.0",
+ "ofetch": "^1.4.1",
+ "ohash": "^2.0.0"
+ }
+ },
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
@@ -17815,6 +18219,106 @@
"@unrs/resolver-binding-win32-x64-msvc": "1.9.2"
}
},
+ "node_modules/unstorage": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.0.tgz",
+ "integrity": "sha512-l9Z7lBiwtNp8ZmcoZ/dmPkFXFdtEdZtTZafCSnEIj3YvtkXeGAtL2rN8MQFy/0cs4eOLpuRJMp9ivdug7TCvww==",
+ "dependencies": {
+ "anymatch": "^3.1.3",
+ "chokidar": "^4.0.3",
+ "destr": "^2.0.5",
+ "h3": "^1.15.4",
+ "lru-cache": "^10.4.3",
+ "node-fetch-native": "^1.6.7",
+ "ofetch": "^1.4.1",
+ "ufo": "^1.6.1"
+ },
+ "peerDependencies": {
+ "@azure/app-configuration": "^1.8.0",
+ "@azure/cosmos": "^4.2.0",
+ "@azure/data-tables": "^13.3.0",
+ "@azure/identity": "^4.6.0",
+ "@azure/keyvault-secrets": "^4.9.0",
+ "@azure/storage-blob": "^12.26.0",
+ "@capacitor/preferences": "^6.0.3 || ^7.0.0",
+ "@deno/kv": ">=0.9.0",
+ "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0",
+ "@planetscale/database": "^1.19.0",
+ "@upstash/redis": "^1.34.3",
+ "@vercel/blob": ">=0.27.1",
+ "@vercel/functions": "^2.2.12",
+ "@vercel/kv": "^1.0.1",
+ "aws4fetch": "^1.0.20",
+ "db0": ">=0.2.1",
+ "idb-keyval": "^6.2.1",
+ "ioredis": "^5.4.2",
+ "uploadthing": "^7.4.4"
+ },
+ "peerDependenciesMeta": {
+ "@azure/app-configuration": {
+ "optional": true
+ },
+ "@azure/cosmos": {
+ "optional": true
+ },
+ "@azure/data-tables": {
+ "optional": true
+ },
+ "@azure/identity": {
+ "optional": true
+ },
+ "@azure/keyvault-secrets": {
+ "optional": true
+ },
+ "@azure/storage-blob": {
+ "optional": true
+ },
+ "@capacitor/preferences": {
+ "optional": true
+ },
+ "@deno/kv": {
+ "optional": true
+ },
+ "@netlify/blobs": {
+ "optional": true
+ },
+ "@planetscale/database": {
+ "optional": true
+ },
+ "@upstash/redis": {
+ "optional": true
+ },
+ "@vercel/blob": {
+ "optional": true
+ },
+ "@vercel/functions": {
+ "optional": true
+ },
+ "@vercel/kv": {
+ "optional": true
+ },
+ "aws4fetch": {
+ "optional": true
+ },
+ "db0": {
+ "optional": true
+ },
+ "idb-keyval": {
+ "optional": true
+ },
+ "ioredis": {
+ "optional": true
+ },
+ "uploadthing": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/unstorage/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
diff --git a/package.json b/package.json
index ef4672bd7..90c2ba604 100644
--- a/package.json
+++ b/package.json
@@ -84,6 +84,7 @@
"execa": "^9.5.2",
"express": "^4.21.2",
"express-rate-limit": "^7.5.0",
+ "fontless": "^0.0.2",
"get-port": "^7.1.0",
"glob": "^11.0.2",
"input-otp": "^1.4.2",
diff --git a/server/index.ts b/server/index.ts
index b55f22cf1..9d56ecc6b 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -28,7 +28,7 @@ const viteDevServer = IS_PROD
server: {
middlewareMode: true,
},
- // We tell Vite we are running a custom app instead of
+ // We tell Vite we are running a custom app instead of
// the SPA default so it doesn't run HTML middleware
appType: 'custom',
}),
@@ -80,6 +80,10 @@ app.use((_, res, next) => {
if (viteDevServer) {
app.use(viteDevServer.middlewares)
+ app.use(
+ '/_fonts',
+ express.static('build/client/_fonts', { immutable: true, maxAge: '1y' }),
+ )
} else {
// Remix fingerprints its assets so we can cache forever.
app.use(
diff --git a/vite.config.ts b/vite.config.ts
index 1f10a67c9..0dab07e39 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -4,6 +4,7 @@ import {
sentryReactRouter,
} from '@sentry/react-router'
import tailwindcss from '@tailwindcss/vite'
+import { fontless } from 'fontless'
import { reactRouterDevTools } from 'react-router-devtools'
import { defineConfig } from 'vite'
import { envOnlyMacros } from 'vite-env-only'
@@ -55,6 +56,7 @@ export default defineConfig((config) => ({
MODE === 'production' && process.env.SENTRY_AUTH_TOKEN
? sentryReactRouter(sentryConfig, config)
: null,
+ fontless(),
],
test: {
include: ['./app/**/*.test.{ts,tsx}'],