Typography
Urbic sets two families: Jost for UI and body, Cormorant Garamond for the display serif the navigation and the large headings use. Both are variable, both self-hosted through Fontsource, both latin-only to keep the payload lean, and both preloaded in BaseHead for first paint.
The font stack
src/styles/fonts.css declares the two @font-face blocks. src/styles/tailwind-theme.css names them:
--font-sans: "JostVariable", "Jost", ui-sans-serif, system-ui, …;
--font-display: "CormorantGaramondVariable", "Cormorant Garamond", ui-serif, Georgia, …;
--font-mono: "SFMono-Regular", "Menlo", "Monaco", …;
Everything inherits --font-sans from html. The serif is opt-in through the font-display utility — it is the nav’s HOME/ABOUT/WORK scale and the large editorial headings, not a body face.
Jost carries the full 100 900 weight axis; Cormorant Garamond carries 300 700. Both are single woff2 files with a unicode-range, so a page downloads two font files and no more.
To swap a family, install its Fontsource package, replace the @font-face block in fonts.css, and repoint --font-sans or --font-display. Update the two preload links in BaseHead to match — a preload for a file nothing loads is a wasted request, and a font with no preload flashes.
To add a subset (cyrillic, latin-ext) or a static weight, the files already ship in the same package: add the @font-face block with its unicode-range to fonts.css.
The shared type classes
Five @apply classes live in @layer components in global.css, and each one earned its place by being written out identically in enough call sites that a home was cheaper than the copies.
.h1, .h2 and .h3 are the ordinary heading ramps. .description is the body-lead scale with its base-700 / dark base-300 colour baked in.
.eyebrow is the small tracked-caps label — 12/20 Jost Medium at 0.08em of tracking. It names a section (“MATERIAL SCHEDULE”), a photograph, a fact row and a pager destination. Its colour is not baked in, because the same scale is base-500 on the page and base-50/75 over a photograph, so it stays a text-* utility at the call site.
.display-lg and .display-xl are the two serif ramps, each named by where it lands at lg — .display-lg covers eight sections, .display-xl seven. Like .eyebrow, they carry no colour and no family: they sit on base-700 on the page and base-50 over a photograph, and some call sites reach them through a tv() that already supplies font-display.
.display-xl is deliberately not every 60px heading on the site. Twelve other places also land on 60px at lg, through six different base and sm ramps. Folding them in would change what they render, so they are left alone. .display-xl is the largest single ramp and the one a new section should reach for rather than inventing a seventh.
The threshold that produced all five is the repo’s own: a class earns an @apply home at roughly four identical call sites. Below that, keep it inline.
The line grid
--row-line: 30px is a site fact. One row of text is 30px tall whatever sits on it, and the design pins the nav’s 32px serif, the footer’s 16px body and the about page’s 18px terms all to it.
Pin it on the font size:
<p class="text-base/(--row-line)">…</p>
Never as a separate leading-* utility. tailwind-merge treats font-size as conflicting with leading and will silently drop the leading-*, which is a bug you find by measuring rather than by reading.
Prose
A project’s body and a journal note’s body are the two places long-form prose renders. Sections/Project/prose.ts carries the project story’s own rules with a check beside it; the journal’s body renders through Sections/Blog/NoteBody.astro.
Neither uses @tailwindcss/typography — the prose styling is the site’s own, which is why the plugin is not a dependency and why the measure, the paragraph rhythm and the heading scale match the rest of the design rather than the plugin’s defaults.
Font features
html sets font-feature-settings: "liga" 1, "calt" 1 — standard ligatures and contextual alternates on. It also sets -webkit-text-size-adjust: none, scrollbar-gutter: stable to prevent the layout shift a scrollbar appearing would cause, and line-height: 1.6 as the document default.