Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions openlibrary/templates/site/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
$# Env-specific favicons (green "D" / orange "T") so dev and testing tabs stand out
<link href="/static/images/openlibrary-$(ol_env)-192x192.png" rel="icon" sizes="192x192" />
<link href="/static/images/openlibrary-$(ol_env)-128x128.png" rel="icon" sizes="128x128" />
$# Self-hosted web fonts (see static/fonts/README.md). crossorigin is
$# mandatory on font preloads (fonts are always fetched anonymous-CORS);
$# without it the preload is refetched and wasted.
<link rel="preload" href="/static/fonts/Literata-latin.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/static/fonts/Inter-latin.woff2" as="font" type="font/woff2" crossorigin>
$# Loaded separately from page-*.css so they cache once and serve every page from cache.
<link href="$static_url('build/css/tokens.css')" rel="stylesheet" type="text/css" />
<link href="$static_url('build/css/ol-components.css')" rel="stylesheet" type="text/css" />
Expand Down
4 changes: 2 additions & 2 deletions static/css/legacy.css
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,7 @@ div.facet .facetHead {
}

div.facetEntry {
font-family: var(--body-family);
font-family: var(--font-family-body);
}

div.facetEntry a {
Expand All @@ -1017,7 +1017,7 @@ div.facetEntry a {

div.facetMoreLess {
flex-basis: 100%;
font-family: var(--body-family);
font-family: var(--font-family-body);
position: relative;
top: -4px;
padding-left: 8px;
Expand Down
1 change: 1 addition & 0 deletions static/css/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@import "tokens/borders.css";
@import "tokens/breakpoints.css";
@import "tokens/colors.css";
@import "tokens/font-faces.css";
@import "tokens/font-families.css";
@import "tokens/line-heights.css";
@import "tokens/spacing.css";
Expand Down
59 changes: 59 additions & 0 deletions static/css/tokens/font-faces.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Web Fonts
* =========
* Self-hosted variable fonts (see static/fonts/README.md for the
* subsetting pipeline):
*
* - Literata — serif for headings, titles, and quotes (wght 400–700,
* opsz 12–36 variable, latin subset)
* - Inter — sans for UI and body text (wght 400–700, latin subset)
*
* Both are preloaded in site/head.html. Non-latin scripts fall back to the
* system stacks via unicode-range. The *-fallback faces are metric-adjusted
* local fonts (Georgia / Arial) so the swap causes minimal layout shift.
*/
@font-face {
font-family: "Literata";
font-style: normal;
font-weight: 400 700;
font-display: swap;
src: url(/static/fonts/Literata-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;
}

@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 400 700;
font-display: swap;
src: url(/static/fonts/Inter-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;
}

/* Metric-adjusted local fallbacks — sized so text set in the fallback
occupies the same space as the web font, minimizing CLS during swap. */
@font-face {
font-family: "Literata-fallback";
font-style: normal;
font-weight: 400 700;
src: local("Georgia");
size-adjust: 108.49%;
ascent-override: 108.49%;
descent-override: 28.39%;
line-gap-override: 0%;
}

@font-face {
font-family: "Inter-fallback";
font-style: normal;
font-weight: 400 700;
src: local("Arial");
size-adjust: 107.89%;
ascent-override: 89.79%;
descent-override: 22.36%;
line-gap-override: 0%;
}
27 changes: 15 additions & 12 deletions static/css/tokens/font-families.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
* ======================
* Raw, base font family definitions.
* Do NOT use these directly in components - use semantic tokens instead.
*
* Literata and Inter are self-hosted web fonts (see tokens/font-faces.css);
* the *-fallback entries are metric-adjusted local fonts that minimize
* layout shift while the web font loads.
*/
:root {
--font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
--font-family-serif: Georgia, "Palatino Linotype", "Book Antiqua", Palatino, serif;
--font-family-mono: "Lucida Console", "Courier New", monospace;
--font-family-sans: "Inter", "Inter-fallback", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
--font-family-serif: "Literata", "Literata-fallback", Georgia, "Palatino Linotype", "Book Antiqua", Palatino, serif;
--font-family-mono: ui-monospace, Menlo, "Lucida Console", "Courier New", monospace;
}

/**
Expand All @@ -16,15 +20,14 @@
* Reference these throughout the app for consistent text styling.
*/
:root {
--font-family-body: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; /* body text, paragraphs, lists */
--font-family-code: Menlo, "Lucida Console", "Courier New", monospace; /* code, pre, inline code */
--font-family-title: Georgia, "Palatino Linotype", "Book Antiqua", Palatino, serif; /* large text in banners and hero sections, book titles */
--font-family-subtitle: Georgia, "Palatino Linotype", "Book Antiqua", Palatino, serif; /* small text in banners and hero sections, book subtitles */
--font-family-heading: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; /* headings in body text */
--font-family-subheading: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; /* subheadings in body text */
--font-family-quote: Georgia, "Palatino Linotype", "Book Antiqua", Palatino, serif; /* quotes */
--font-family-button: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; /* buttons */

--font-family-body: var(--font-family-sans); /* body text, paragraphs, lists */
--font-family-code: var(--font-family-mono); /* code, pre, inline code */
--font-family-title: var(--font-family-serif); /* large text in banners and hero sections, book titles */
--font-family-subtitle: var(--font-family-serif); /* small text in banners and hero sections, book subtitles */
--font-family-heading: var(--font-family-sans); /* headings in body text */
--font-family-subheading: var(--font-family-sans); /* subheadings in body text */
--font-family-quote: var(--font-family-serif); /* quotes */
--font-family-button: var(--font-family-sans); /* buttons */
}

/**
Expand Down
Binary file added static/fonts/Inter-latin.woff2
Binary file not shown.
Binary file added static/fonts/Literata-latin.woff2
Binary file not shown.
43 changes: 43 additions & 0 deletions static/fonts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Self-hosted web fonts

| File | Family | Axes kept | Subset | Size |
|---|---|---|---|---|
| `Literata-latin.woff2` | [Literata](https://github.com/googlefonts/literata) (OFL) | `wght 400–700`, `opsz 12–36` | latin | ~55 KB |
| `Inter-latin.woff2` | [Inter](https://github.com/rsms/inter) (OFL) | `wght 400–700` (`opsz` pinned at 14) | latin | ~50 KB |

Literata is the serif for headings, book titles, and quotes; Inter is the
sans for UI and body text. `@font-face` rules (including metric-adjusted
local fallbacks that minimize CLS) live in `static/css/tokens/font-faces.css`,
and both files are preloaded in `openlibrary/templates/site/head.html`.

Italics are not shipped — browsers synthesize an oblique, which is acceptable
for the small amount of italic text. Non-latin scripts fall back to the
system stacks via `unicode-range`.

## Rebuilding the subsets

Source: the variable TTFs from the [google/fonts](https://github.com/google/fonts)
repo (`ofl/literata/Literata[opsz,wght].ttf`, `ofl/inter/Inter[opsz,wght].ttf`).

```bash
LATIN="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"

# 1. Restrict the variation space (fonttools varLib.instancer)
uvx --from "fonttools[woff]" --with brotli fonttools varLib.instancer \
-o Literata-inst.ttf "Literata[opsz,wght].ttf" "wght=400:700" "opsz=12:36"
uvx --from "fonttools[woff]" --with brotli fonttools varLib.instancer \
-o Inter-inst.ttf "Inter[opsz,wght].ttf" "wght=400:700" "opsz=14"

# 2. Subset to latin and compress to woff2 (pyftsubset)
uvx --from "fonttools[woff]" --with brotli pyftsubset Literata-inst.ttf \
--unicodes="$LATIN" --flavor=woff2 --output-file=Literata-latin.woff2
uvx --from "fonttools[woff]" --with brotli pyftsubset Inter-inst.ttf \
--unicodes="$LATIN" --flavor=woff2 --output-file=Inter-latin.woff2
```

The fallback metrics in `font-faces.css` (`size-adjust` / `ascent-override` /
`descent-override`) come from [fontpie](https://github.com/pixel-point/fontpie)
(`npx fontpie <file>.woff2`) for Inter/Arial, and the same
frequency-weighted-average-width method computed against Georgia for
Literata (fontpie has no Georgia preset). Recompute them if the font files
change.