feat(brand): Zen is the family, and the faces are @hanzo/font's

typography.ts named Geist Sans and Geist Mono in three stacks and two code
styles, and shipped a `fontFaces` string declaring @font-face against
/fonts/geist-sans-var.woff2 and /fonts/geist-mono-var.woff2 — files this package
has never contained, so it could only ever paint the fallback. Nothing imported
it. Deleted rather than repointed: the faces belong to @hanzo/font, which ships
Zen, Zen Mono and the five Zen Pixel variants with the rules to match, and a
second declaration is how two of them come to disagree.

The SVG card renderers each declared the same font stack, both leading with
Inter — a face this brand has never owned. One `svgFontStack`, imported by both,
leading with Zen. The tail is unchanged, so a rasterizer that cannot resolve Zen
falls through exactly as before and this cannot render worse than what it
replaced; point resvg-js at @hanzo/font to get the real face.

type-check + build green, 20/20 tests pass.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-24 11:08:07 -07:00
co-authored by Hanzo Dev
parent f2bee83747
commit dc6ae29a2a
5 changed files with 27 additions and 26 deletions
+1 -1
View File
@@ -72,7 +72,7 @@ green `#00A652` for success).
## 3. Typography
### 3.1 Family
Geist Sans + Geist Mono (loaded via `next/font/google`)
Zen + Zen Mono, self-hosted by `@hanzo/font` (no third-party font host)
### 3.2 Type scale (Tailwind classes)
+1 -1
View File
@@ -94,7 +94,7 @@ src/
registry.ts # BrandIdentity, BRANDS, getBrand, toBrandContract, BrandContract
logos.tsx # HanzoLogo/HanzoLogoSVG/HanzoFavicon (consume @hanzo/logo) + BrandLogo
colors.ts # Monochrome palette: Hanzo Black (#0a0a0b) ↔ Hanzo White (#fff), neutral scale, status semantics
typography.ts # Font families (Geist Sans, Geist Mono), sizes, weights, line heights
typography.ts # Font families (Zen, Zen Mono), sizes, weights, line heights
themes.ts # Light/dark theme definitions, applyTheme() helper
tokens.ts # Design tokens (spacing, radius, shadows, transitions)
config.ts # Company info, social links, domains, SEO defaults
+2 -1
View File
@@ -10,11 +10,12 @@
* rsvg-convert). Add new card types here, not per-repo.
*/
import { MARK_PATHS, MARK_VIEWBOX } from '@hanzo/logo'
import { svgFontStack } from './typography'
const W = 1200
const H = 630
const PAD = 88
const FONT = `'Inter','SF Pro Display','Helvetica Neue',Arial,sans-serif`
const FONT = svgFontStack
const MARK_SIZE = Number(MARK_VIEWBOX.split(' ')[2]) || 67
/** Per-brand palette. Defaults are pure monochrome — no brand is hardcoded. */
+2 -1
View File
@@ -9,6 +9,7 @@
* SVG→PNG renderer (resvg-js, satori, sharp, rsvg-convert).
*/
import { MARK_PATHS, MARK_VIEWBOX } from '@hanzo/logo'
import { svgFontStack } from './typography'
export interface HeroOptions {
/** Repo / product / package name, e.g. `@hanzo/ai` or `Hanzo Node`. */
@@ -92,7 +93,7 @@ export function renderHero(opts: HeroOptions): string {
const pad = 88
const markSize = 76
const font = `'Inter','SF Pro Display','Helvetica Neue',Arial,sans-serif`
const font = svgFontStack
const titleSize = title.length > 26 ? 64 : 80
void accentColors // accent now reads only via the glow backdrop, not a rule
+21 -22
View File
@@ -5,10 +5,10 @@
export const typography = {
// Font families
fontFamily: {
sans: ['Geist Sans', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
sans: ['Zen', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
serif: ['Georgia', 'Cambria', 'Times New Roman', 'Times', 'serif'],
mono: ['Geist Mono', 'Monaco', 'Consolas', 'monospace'],
display: ['Geist Sans', 'system-ui', 'sans-serif'],
mono: ['Zen Mono', 'ui-monospace', 'SFMono-Regular', 'Monaco', 'Consolas', 'monospace'],
display: ['Zen', 'ui-sans-serif', 'system-ui', 'sans-serif'],
},
// Font sizes
@@ -130,7 +130,7 @@ export const typography = {
// Code styles
code: {
inline: {
fontFamily: 'Geist Mono, monospace',
fontFamily: 'Zen Mono, ui-monospace, monospace',
fontSize: '0.875em',
fontWeight: '500',
backgroundColor: '#F5F5F5',
@@ -138,7 +138,7 @@ export const typography = {
borderRadius: '0.25rem',
},
block: {
fontFamily: 'Geist Mono, monospace',
fontFamily: 'Zen Mono, ui-monospace, monospace',
fontSize: '0.875rem',
fontWeight: '400',
lineHeight: '1.5',
@@ -149,23 +149,22 @@ export const typography = {
},
} as const
// Font face declarations for custom fonts
export const fontFaces = `
@font-face {
font-family: 'Geist Sans';
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('/fonts/geist-sans-var.woff2') format('woff2-variations');
}
/**
* The stack the SVG card renderers ask for, as a CSS value rather than an array.
* It lived twice — `cards.ts` and `hero.ts` each declared the same string, both
* leading with Inter, which is not a face this brand has ever owned.
*
* Zen leads. @hanzo/font ships the woff2, so a rasterizer resolves it by being
* pointed at that package (resvg-js takes `fontFiles`/`fontDirs`); a host that
* cannot falls through the tail exactly as it did before, so naming Zen here
* cannot render worse than the string it replaced.
*/
export const svgFontStack = `Zen,'SF Pro Display','Helvetica Neue',Arial,sans-serif`
@font-face {
font-family: 'Geist Mono';
font-style: normal;
font-weight: 100 800;
font-display: swap;
src: url('/fonts/geist-mono-var.woff2') format('woff2-variations');
}
`
// The FACES are @hanzo/font's — it ships Zen, Zen Mono and the five Zen Pixel
// variants as woff2 with the @font-face rules to match. This package names the
// family a surface should ask for; it does not declare one, and it used to:
// `fontFaces` pointed two @font-face rules at /fonts/geist-*-var.woff2, files
// this package has never shipped, so it could only ever paint the fallback.
export default typography