Skip to content

Commit fa181d9

Browse files
zachdunnclaude
andauthored
fix(web): Geist Pixel via the variable font, drop faux-bold weight (#2128)
* fix(web): Geist Pixel via the variable font, drop faux-bold weight The npm geist package only ships five static 500-weight instance files for the pixel faces, and every .font-pixel usage stacked font-bold/font-semibold on top — the browser synthesized the bold, smearing the bitmap grid. Replace it with the official variable woff2 from vercel.com/font (SIL OFL 1.1, ~39KB, self-hosted under web/src/app/fonts/geist-pixel/): single weight 400 with the ELSH element-shape axis, pinned to Square (1) in .font-pixel along with font-weight: 400 and font-synthesis: none. Remove the now-inert bold utilities from the pixel headings and drop the geist dependency. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(web): stop forcing aliased rendering on Geist Pixel The face's "pixels" are vector squares, not a bitmap; -webkit-font-smoothing: none snapped each square to whole device pixels at inconsistent sizes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(web): drop the pixel face from 20px section titles At text-xl each pixel square spans ~1.5 device pixels and the grid goes mushy; PageHeader (catalog/collections/categories) and the /live title go back to the pre-#2126 bold sans. The pixel accent stays on the wordmark, entity titles (24px+), and doc h1s, where the grid resolves cleanly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 2532825 commit fa181d9

13 files changed

Lines changed: 136 additions & 31 deletions

File tree

bun.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
"@shikijs/themes": "^4.3.1",
2424
"@tailwindcss/typography": "^0.5.20",
2525
"better-auth": "^1.6.23",
26-
"geist": "1.7.2",
2726
"github-slugger": "^2.0.0",
2827
"gray-matter": "^4.0.3",
2928
"next": "^16.2.10",

web/src/app/[orgSlug]/(org)/layout.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@ export default async function OrgLayout({
9494
</span>
9595
)}
9696
<div className="min-w-0 grow basis-[180px] pt-px">
97-
<h1 className="font-pixel text-[27px] font-semibold leading-tight text-[var(--fg)]">
98-
{org.name}
99-
</h1>
97+
<h1 className="font-pixel text-[27px] leading-tight text-[var(--fg)]">{org.name}</h1>
10098
{metaParts.length > 0 && (
10199
<div className="mt-2 flex flex-wrap items-center gap-x-2.5 gap-y-1 text-[13px] text-[var(--fg-3)]">
102100
{metaParts.map((part, i) => (

web/src/app/[orgSlug]/[slug]/_views/product-view.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export async function ProductView({
172172
<div className="flex-1 min-w-0">
173173
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-3">
174174
<div className="min-w-0">
175-
<h1 className="font-pixel text-[28px] font-bold text-stone-900 dark:text-stone-100">
175+
<h1 className="font-pixel text-[28px] text-stone-900 dark:text-stone-100">
176176
{product.name}
177177
</h1>
178178
{product.description && (
38.2 KB
Binary file not shown.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
Copyright 2026 The Geist Project Authors (https://github.com/vercel/geist-font)
2+
3+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
4+
This license is copied below, and is also available with a FAQ at:
5+
https://openfontlicense.org
6+
7+
8+
-----------------------------------------------------------
9+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10+
-----------------------------------------------------------
11+
12+
PREAMBLE
13+
The goals of the Open Font License (OFL) are to stimulate worldwide
14+
development of collaborative font projects, to support the font creation
15+
efforts of academic and linguistic communities, and to provide a free and
16+
open framework in which fonts may be shared and improved in partnership
17+
with others.
18+
19+
The OFL allows the licensed fonts to be used, studied, modified and
20+
redistributed freely as long as they are not sold by themselves. The
21+
fonts, including any derivative works, can be bundled, embedded,
22+
redistributed and/or sold with any software provided that any reserved
23+
names are not used by derivative works. The fonts and derivatives,
24+
however, cannot be released under any other type of license. The
25+
requirement for fonts to remain under this license does not apply
26+
to any document created using the fonts or their derivatives.
27+
28+
DEFINITIONS
29+
"Font Software" refers to the set of files released by the Copyright
30+
Holder(s) under this license and clearly marked as such. This may
31+
include source files, build scripts and documentation.
32+
33+
"Reserved Font Name" refers to any names specified as such after the
34+
copyright statement(s).
35+
36+
"Original Version" refers to the collection of Font Software components as
37+
distributed by the Copyright Holder(s).
38+
39+
"Modified Version" refers to any derivative made by adding to, deleting,
40+
or substituting -- in part or in whole -- any of the components of the
41+
Original Version, by changing formats or by porting the Font Software to a
42+
new environment.
43+
44+
"Author" refers to any designer, engineer, programmer, technical
45+
writer or other person who contributed to the Font Software.
46+
47+
PERMISSION & CONDITIONS
48+
Permission is hereby granted, free of charge, to any person obtaining
49+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
50+
redistribute, and sell modified and unmodified copies of the Font
51+
Software, subject to the following conditions:
52+
53+
1) Neither the Font Software nor any of its individual components,
54+
in Original or Modified Versions, may be sold by itself.
55+
56+
2) Original or Modified Versions of the Font Software may be bundled,
57+
redistributed and/or sold with any software, provided that each copy
58+
contains the above copyright notice and this license. These can be
59+
included either as stand-alone text files, human-readable headers or
60+
in the appropriate machine-readable metadata fields within text or
61+
binary files as long as those fields can be easily viewed by the user.
62+
63+
3) No Modified Version of the Font Software may use the Reserved Font
64+
Name(s) unless explicit written permission is granted by the corresponding
65+
Copyright Holder. This restriction only applies to the primary font name as
66+
presented to the users.
67+
68+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69+
Software shall not be used to promote, endorse or advertise any
70+
Modified Version, except to acknowledge the contribution(s) of the
71+
Copyright Holder(s) and the Author(s) or with their explicit written
72+
permission.
73+
74+
5) The Font Software, modified or unmodified, in part or in whole,
75+
must be distributed entirely under this license, and must not be
76+
distributed under any other license. The requirement for fonts to
77+
remain under this license does not apply to any document created
78+
using the Font Software.
79+
80+
TERMINATION
81+
This license becomes null and void if any of the above conditions are
82+
not met.
83+
84+
DISCLAIMER
85+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93+
OTHER DEALINGS IN THE FONT SOFTWARE.

web/src/app/globals.css

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,28 +70,35 @@
7070
background-color: oklch(0.444 0.011 286.3); /* stone-700 */
7171
}
7272

73-
/* Geist Pixel display accent (geist/font/pixel, Square variant — bound in
74-
layout.tsx). Used on the header wordmark and short, fixed page/entity titles
75-
(org + product names, section-index labels, static/legal/docs page titles),
76-
NOT on running heads or long release titles where the bitmap grid hurts
77-
legibility. It's a semi-mono bitmap face, so pin letter-spacing to 0
78-
(overriding any `tracking-*`) and disable subpixel smoothing so the pixels
79-
stay crisp; JetBrains Mono is the fallback weight.
73+
/* Geist Pixel display accent (self-hosted variable font, bound in layout.tsx;
74+
`font-variation-settings: "ELSH" 1` pins the Square shape — 0 Regular ·
75+
1 Square · 20 Circle · 40 Grid · 60 Triangle · 80 Line). Used on the header
76+
wordmark and short, fixed page/entity titles (org + product names,
77+
section-index labels, static/legal/docs page titles), NOT on running heads or
78+
long release titles where the bitmap grid hurts legibility. It's a
79+
single-weight (400) semi-mono face, so pin font-weight to 400 and disable
80+
synthesis (a `font-bold` utility would otherwise smear the pixels with faux
81+
bold), and pin letter-spacing to 0 (overriding any `tracking-*`). The
82+
"pixels" are vector squares, not a bitmap — leave font smoothing alone;
83+
`-webkit-font-smoothing: none` renders each square at inconsistent sizes.
84+
JetBrains Mono is the fallback weight.
8085
8186
`.font-pixel` is the direct opt-in for JSX headings. `.pixel-doc-title h1`
8287
is the prose-scoped variant: markdown pages (privacy, terms, security, docs)
8388
render their title as the body's leading `# …`, so the class goes on the
8489
wrapping <article> and only re-fonts that top-level h1. */
8590
:root {
8691
--font-pixel:
87-
var(--font-geist-pixel-square), var(--font-jetbrains-mono), ui-monospace, SFMono-Regular, Menlo,
92+
var(--font-geist-pixel), var(--font-jetbrains-mono), ui-monospace, SFMono-Regular, Menlo,
8893
Monaco, "Liberation Mono", "Courier New", monospace;
8994
}
9095
.font-pixel,
9196
.pixel-doc-title h1 {
9297
font-family: var(--font-pixel);
98+
font-variation-settings: "ELSH" 1;
99+
font-weight: 400;
100+
font-synthesis: none;
93101
letter-spacing: 0;
94-
-webkit-font-smoothing: none;
95102
}
96103

97104
/* ── Code (inline + blocks) ── */

web/src/app/layout.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference types="react/canary" />
22
import type { Metadata } from "next";
33
import { JetBrains_Mono } from "next/font/google";
4-
import { GeistPixelSquare } from "geist/font/pixel";
4+
import localFont from "next/font/local";
55
import Script from "next/script";
66
import { ViewTransition } from "react";
77
import { ThemeProvider } from "@/components/theme-provider";
@@ -23,6 +23,21 @@ const jetbrainsMono = JetBrains_Mono({
2323
display: "swap",
2424
});
2525

26+
// Geist Pixel, self-hosted as the official VARIABLE woff2 from
27+
// https://vercel.com/font?type=pixel (SIL OFL 1.1 — OFL.txt alongside). The npm
28+
// `geist` package only ships five static 500-weight instance files; the variable
29+
// build is a single ~39KB file at weight 400 with a custom ELSH ("Element
30+
// Shape") axis — 0 Regular · 1 Square · 20 Circle · 40 Grid · 60 Triangle ·
31+
// 80 Line. `.font-pixel` in globals.css pins the Square shape via
32+
// `font-variation-settings`.
33+
const geistPixel = localFont({
34+
src: "./fonts/geist-pixel/GeistPixel-Variable.woff2",
35+
variable: "--font-geist-pixel",
36+
weight: "400",
37+
display: "swap",
38+
adjustFontFallback: false,
39+
});
40+
2641
export const metadata: Metadata = {
2742
title: {
2843
default: "releases.sh - Product releases and changelogs, indexed for agents",
@@ -68,12 +83,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
6883
// landing partway down the destination instead of at the top. The attribute
6984
// restores Next's instant scroll-to-top on route changes while keeping smooth
7085
// scrolling for in-page anchors. See the Next.js 16 upgrade guide.
71-
// GeistPixelSquare only registers the `--font-geist-pixel-square` CSS variable
72-
// here; the `.font-pixel` class in globals.css binds it to the wordmark + short
73-
// entity titles. Nothing renders pixel unless an element opts in.
74-
const htmlClassName = ["scroll-smooth", jetbrainsMono.variable, GeistPixelSquare.variable].join(
75-
" ",
76-
);
86+
// geistPixel only registers the `--font-geist-pixel` CSS variable here; the
87+
// `.font-pixel` class in globals.css binds it to the wordmark + short entity
88+
// titles. Nothing renders pixel unless an element opts in.
89+
const htmlClassName = ["scroll-smooth", jetbrainsMono.variable, geistPixel.variable].join(" ");
7790

7891
return (
7992
<html

web/src/app/live/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export default function LivePage() {
2828
return (
2929
<div className="min-h-screen">
3030
<div className="max-w-3xl mx-auto px-6 pt-8 pb-12">
31-
<h1 className="font-pixel text-xl font-bold text-stone-900 dark:text-stone-100 mb-2">
31+
<h1 className="text-xl font-bold tracking-tight text-stone-900 dark:text-stone-100 mb-2">
3232
{TITLE}
3333
</h1>
3434
<p className="text-sm text-stone-600 dark:text-stone-400 mb-6">

web/src/app/search/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default async function SearchPage({
4444
<SearchProvider initialQuery={q ?? ""} initialResults={results} initialRange={range}>
4545
<div className="min-h-screen">
4646
<div className="max-w-2xl mx-auto px-6 pt-12 pb-12">
47-
<h1 className="font-pixel text-2xl font-semibold mb-4">Search</h1>
47+
<h1 className="font-pixel text-2xl mb-4">Search</h1>
4848
<SearchBar />
4949
<div className="mt-2 flex justify-end">
5050
<SearchTimeframe />

0 commit comments

Comments
 (0)