Generator of metric-adjusted fallback font stacks with live preview and layout-shift (CLS) estimation. Static Astro site. Scope and roadmap: docs/plan.md. Contribution rules: AGENTS.md.
Source is organized by FEOD levels under src/:
| Level | Path | Role |
|---|---|---|
app |
src/app |
Astro srcDir: routes (src/app/pages), layouts, styles, i18n wiring. Composes everything below. |
pages |
src/pages |
Page entities, each with a root index.ts. Routes in src/app/pages render them. |
modules |
src/modules |
Product modules with a root index.ts public API. |
common |
src/common |
Reusable entities not tied to a product scenario. |
global |
src/global |
Environment declarations and shims. Never imported by application code. |
Rules: imports follow the import matrix and go through an entity's root index.ts. Enforced by pnpm lint:arch (@feod/analyzer) and by Biome noRestrictedImports overrides in biome.json.
Known limits of the checks:
@feod/analyzerdoes not parse.astrofiles. Biome matches@/alias imports there, andpnpm lint:archadditionally rejects relative path literals in.astrofiles that leave their entity (scripts/lint-arch.ts). Keep logic in.ts/.tsxand use@/imports in.astrofiles.- The Biome rules ban alias imports of an entity's internals, including your own (
@/common/ui/xfrom insidesrc/common/ui); inside an entity use relative imports.
Astro treats src/pages as the routes folder by default; srcDir: ./src/app in astro.config.mjs moves routes to src/app/pages so that FEOD pages can live at src/pages.
Paraglide JS compiles messages/<locale>.json into typed message functions (src/common/i18n/paraglide/, generated, gitignored). All locales are URL-prefixed (/en/, /ru/); / redirects to the browser's language, falling back to English.
- Use messages through
messagesFor(locale)from@/common/i18n; a missing translation key is a TypeScript error (catalog.ts) and a failing unit test. - Add a language:
pnpm i18n:add <locale>, then translatemessages/<locale>.jsonand runpnpm lint:fix. Language names in the switcher come fromIntl.DisplayNames.
Requires Node 24 (.node-version) and pnpm (packageManager in package.json).
pnpm install # also installs git hooks
pnpm dev
pnpm build
pnpm check # typecheck
pnpm lint # Biome
pnpm lint:arch # FEOD boundaries
pnpm test # unit tests (Vitest)
pnpm test:e2e # e2e + accessibility (Playwright, axe); first: pnpm exec playwright install chromium
pnpm verify # lint + architecture + typecheck + unit tests + build + performance budget
pnpm perf # initial JavaScript budget (after a build)- Upload WOFF2/WOFF/TTF/OTF/TTC fonts, paste
@font-faceCSS, or pick a Google Fonts family; everything is parsed locally in a worker. - Per-system fallbacks (Windows, macOS, Linux, ChromeOS, Android, iOS) chosen from a dataset of preinstalled fonts, with
size-adjustand the vertical overrides fitted automatically and an estimate of the layout shift per browser of your audience. - Browser support matrix from a browserslist query, usage statistics or a manual system mix.
- Export as CSS, CSS custom properties, Tailwind v4, Next.js, Nuxt Fonts, Astro, JSON, preload tags, a Font Loading API script, or a Markdown impact report.
- Share links (
#s=...), local persistence of fonts and settings, presets, a system font catalog, a guide.
Generated data is committed; the scripts need network access (and, for Windows metrics, a Windows machine):
| Script | Output |
|---|---|
pnpm os-fonts:import |
src/modules/os-fonts/data/os-fonts.json (availability per OS from vendor sources) |
pnpm os-fonts:metrics |
metrics.json and variants.json (glyph advances and vertical metrics) |
pnpm compat:build |
src/modules/audience/data/descriptor-support.json (MDN browser-compat-data) |
pnpm google-fonts:catalog |
src/modules/font-sources/data/google-catalog.json |
pnpm fontsource:catalog |
src/modules/font-sources/data/fontsource-catalog.json |
pnpm unicode:blocks |
src/modules/font-metrics/data/blocks.json |
pnpm og:build |
public/og.png (needs Playwright Chromium) |
Every source is listed in CREDITS.md, which is also rendered as the Credits page.
Work happens on issues in fogrew/fontstay.dev; see AGENTS.md for the rules in full.
- Branches follow Conventional Branch (
feature/<issue>-<desc>,bugfix/...,chore/...,release/v<semver>,hotfix/v<semver>) offdevelop. - Commits are Conventional Commits, GPG-signed. Hooks (lefthook, installed by
pnpm install):pre-commitruns Biome on staged files, the FEOD check andastro check;commit-msgruns commitlint. - Merge locally (
git rebase develop <branch>, thengit merge --ff-only) and push; do not use the GitHub merge button, which strips signatures. - Releases:
git switch -c release/v<semver> develop,pnpm release <semver>(bumpspackage.jsonand stampsCHANGELOG.md, generated by git-cliff from the commit history), commitchore(release): v<semver>, merge intomainwith--no-ff, signed tagv<semver>, merge back intodevelop.
The site is static and deployed with Cloudflare Workers Builds (main is production, other branches get previews such as develop.fontstay.dev, which are noindex). It runs under a strict Content Security Policy; see docs/security.md. The only network access is the optional Google Fonts picker. Performance budget and lazy chunks: docs/performance.md.