@hanzo/design 0.5.17 → 0.5.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/assets/fonts/{LICENSE-Geist.txt → LICENSE-Zen.txt} +1 -0
- package/assets/fonts/Zen-Variable.woff2 +0 -0
- package/assets/fonts/ZenMono-Variable.woff2 +0 -0
- package/dist/tokens.gen.cjs +4 -4
- package/dist/tokens.gen.d.ts +4 -4
- package/dist/tokens.gen.js +4 -4
- package/guidelines/DESIGN.md +36 -26
- package/package.json +2 -3
- package/prompts/studio-designer.md +9 -2
- package/scripts/check-tokens.mjs +6 -27
- package/scripts/gen-tokens.mjs +0 -78
- package/src/tokens.gen.ts +4 -4
- package/styles.css +40 -17
- package/tokens/fonts.css +39 -16
- package/assets/fonts/Geist-Variable.woff2 +0 -0
- package/assets/fonts/GeistMono-Variable.woff2 +0 -0
- package/tailwind.css +0 -1162
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ import { HanzoLogo } from "@hanzo/design/components/core/HanzoLogo.jsx";
|
|
|
81
81
|
| `content/` | The words — brand voice and taglines. |
|
|
82
82
|
| `docs/` | How to use the system — integrate the tokens, theme, extend. |
|
|
83
83
|
| `guidelines/` | Specimen cards — color, type, spacing, brand, iconography — the visual reference. |
|
|
84
|
-
| `assets/fonts/` |
|
|
84
|
+
| `assets/fonts/` | Zen + Zen Mono, self-hosted (two variable `.woff2`, 141 KB, SIL OFL-1.1). No Google Fonts request. |
|
|
85
85
|
| `assets/` | The mark, wordmark, favicon, provider + partner logos, brand imagery. |
|
|
86
86
|
| `scripts/check-tokens.mjs` | The gate: every token file is served, every internal `var()` resolves, and the contrast floors hold. Runs on `npm run build`. |
|
|
87
87
|
| `ui_kits/` | Composed surfaces (e.g. `SiteChrome`) assembled from the components. |
|
|
Binary file
|
|
Binary file
|
package/dist/tokens.gen.cjs
CHANGED
|
@@ -303,9 +303,9 @@ exports.zIndex = {
|
|
|
303
303
|
};
|
|
304
304
|
/** fonts tokens (from tokens/fonts.css). Values are raw CSS. */
|
|
305
305
|
exports.fonts = {
|
|
306
|
-
'font-sans': '"
|
|
306
|
+
'font-sans': '"Zen",ui-sans-serif,system-ui,sans-serif',
|
|
307
307
|
'font-display': 'var(--font-sans)',
|
|
308
|
-
'font-mono': '"
|
|
308
|
+
'font-mono': '"Zen Mono",ui-monospace,SFMono-Regular,monospace',
|
|
309
309
|
'font-serif': 'Georgia,serif',
|
|
310
310
|
'font-feature-settings': '"ss01","ss02","cv01","cv02","cv03"',
|
|
311
311
|
};
|
|
@@ -588,9 +588,9 @@ exports.cssVars = {
|
|
|
588
588
|
'--z-toast': '800',
|
|
589
589
|
'--z-tooltip': 'var(--z-popover)',
|
|
590
590
|
'--z-notification': 'var(--z-toast)',
|
|
591
|
-
'--font-sans': '"
|
|
591
|
+
'--font-sans': '"Zen",ui-sans-serif,system-ui,sans-serif',
|
|
592
592
|
'--font-display': 'var(--font-sans)',
|
|
593
|
-
'--font-mono': '"
|
|
593
|
+
'--font-mono': '"Zen Mono",ui-monospace,SFMono-Regular,monospace',
|
|
594
594
|
'--font-serif': 'Georgia,serif',
|
|
595
595
|
'--font-feature-settings': '"ss01","ss02","cv01","cv02","cv03"',
|
|
596
596
|
'--ring-offset': '-2px',
|
package/dist/tokens.gen.d.ts
CHANGED
|
@@ -297,9 +297,9 @@ export declare const zIndex: {
|
|
|
297
297
|
};
|
|
298
298
|
/** fonts tokens (from tokens/fonts.css). Values are raw CSS. */
|
|
299
299
|
export declare const fonts: {
|
|
300
|
-
readonly 'font-sans': "\"
|
|
300
|
+
readonly 'font-sans': "\"Zen\",ui-sans-serif,system-ui,sans-serif";
|
|
301
301
|
readonly 'font-display': "var(--font-sans)";
|
|
302
|
-
readonly 'font-mono': "\"
|
|
302
|
+
readonly 'font-mono': "\"Zen Mono\",ui-monospace,SFMono-Regular,monospace";
|
|
303
303
|
readonly 'font-serif': "Georgia,serif";
|
|
304
304
|
readonly 'font-feature-settings': "\"ss01\",\"ss02\",\"cv01\",\"cv02\",\"cv03\"";
|
|
305
305
|
};
|
|
@@ -582,9 +582,9 @@ export declare const cssVars: {
|
|
|
582
582
|
readonly '--z-toast': "800";
|
|
583
583
|
readonly '--z-tooltip': "var(--z-popover)";
|
|
584
584
|
readonly '--z-notification': "var(--z-toast)";
|
|
585
|
-
readonly '--font-sans': "\"
|
|
585
|
+
readonly '--font-sans': "\"Zen\",ui-sans-serif,system-ui,sans-serif";
|
|
586
586
|
readonly '--font-display': "var(--font-sans)";
|
|
587
|
-
readonly '--font-mono': "\"
|
|
587
|
+
readonly '--font-mono': "\"Zen Mono\",ui-monospace,SFMono-Regular,monospace";
|
|
588
588
|
readonly '--font-serif': "Georgia,serif";
|
|
589
589
|
readonly '--font-feature-settings': "\"ss01\",\"ss02\",\"cv01\",\"cv02\",\"cv03\"";
|
|
590
590
|
readonly '--ring-offset': "-2px";
|
package/dist/tokens.gen.js
CHANGED
|
@@ -300,9 +300,9 @@ export const zIndex = {
|
|
|
300
300
|
};
|
|
301
301
|
/** fonts tokens (from tokens/fonts.css). Values are raw CSS. */
|
|
302
302
|
export const fonts = {
|
|
303
|
-
'font-sans': '"
|
|
303
|
+
'font-sans': '"Zen",ui-sans-serif,system-ui,sans-serif',
|
|
304
304
|
'font-display': 'var(--font-sans)',
|
|
305
|
-
'font-mono': '"
|
|
305
|
+
'font-mono': '"Zen Mono",ui-monospace,SFMono-Regular,monospace',
|
|
306
306
|
'font-serif': 'Georgia,serif',
|
|
307
307
|
'font-feature-settings': '"ss01","ss02","cv01","cv02","cv03"',
|
|
308
308
|
};
|
|
@@ -585,9 +585,9 @@ export const cssVars = {
|
|
|
585
585
|
'--z-toast': '800',
|
|
586
586
|
'--z-tooltip': 'var(--z-popover)',
|
|
587
587
|
'--z-notification': 'var(--z-toast)',
|
|
588
|
-
'--font-sans': '"
|
|
588
|
+
'--font-sans': '"Zen",ui-sans-serif,system-ui,sans-serif',
|
|
589
589
|
'--font-display': 'var(--font-sans)',
|
|
590
|
-
'--font-mono': '"
|
|
590
|
+
'--font-mono': '"Zen Mono",ui-monospace,SFMono-Regular,monospace',
|
|
591
591
|
'--font-serif': 'Georgia,serif',
|
|
592
592
|
'--font-feature-settings': '"ss01","ss02","cv01","cv02","cv03"',
|
|
593
593
|
'--ring-offset': '-2px',
|
package/guidelines/DESIGN.md
CHANGED
|
@@ -15,42 +15,52 @@ and the dark-black palette.
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
## 1. Typography —
|
|
18
|
+
## 1. Typography — Zen
|
|
19
19
|
|
|
20
20
|
| Role | Family | Notes |
|
|
21
21
|
|------|--------|-------|
|
|
22
|
-
| UI / body / display / heading (`sans`) | **
|
|
23
|
-
| code / data / mono (`mono`) | **
|
|
22
|
+
| UI / body / display / heading (`sans`) | **Zen** | Variable, `wght` 100–900. One file covers every weight. |
|
|
23
|
+
| code / data / mono (`mono`) | **Zen Mono** | Variable, same axis. |
|
|
24
|
+
| display accents | **Zen Pixel** | Five cuts: Circle, Grid, Line, Square, Triangle. |
|
|
24
25
|
| Arabic / Hebrew (`--font-ar` / `--font-he`) | unchanged | i18n only — keep. |
|
|
25
26
|
|
|
26
|
-
**
|
|
27
|
+
**There is one family.** No Basel Grotesk, no Druk, no Inter, DM Sans, Figtree,
|
|
28
|
+
PT Sans or Roboto Mono — and no licensed third-party face at all, which is the
|
|
29
|
+
point: Zen is ours, so nothing here is a seat count or a domain licence.
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`@font-face` (weights 400/500, `font-display: swap`, `font-style: normal`):
|
|
31
|
+
**An app writes nothing.** `@hanzo/design/styles.css` declares the `@font-face`
|
|
32
|
+
rules and serves the binaries out of this package, so importing the token layer
|
|
33
|
+
is the whole integration. Do NOT hand-write an `@font-face`, do NOT reach for
|
|
34
|
+
`next/font/google`, and do NOT add a per-app copy of the files.
|
|
33
35
|
|
|
34
36
|
```css
|
|
35
|
-
@
|
|
36
|
-
font-family: 'Basel';
|
|
37
|
-
font-style: normal;
|
|
38
|
-
font-weight: 400; /* Book; 500 = Medium */
|
|
39
|
-
font-display: swap;
|
|
40
|
-
src: url('.../Basel-Grotesk-Book.woff2') format('woff2'),
|
|
41
|
-
url('.../Basel-Grotesk-Book.woff') format('woff');
|
|
42
|
-
}
|
|
37
|
+
@import "@hanzo/design/styles.css"; /* faces + tokens, one line */
|
|
43
38
|
```
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
Then name the role, never the face: `var(--font-sans)`, `var(--font-mono)`.
|
|
41
|
+
|
|
42
|
+
### Brand presets
|
|
43
|
+
|
|
44
|
+
`@hanzo/font/presets.css` carries five settings, each a point in Zen's own
|
|
45
|
+
parameter space that a brand has settled on. Two were FITTED by pixel residual
|
|
46
|
+
against the faces they replace, so the swap holds on metrics rather than on
|
|
47
|
+
taste.
|
|
48
|
+
|
|
49
|
+
| preset | wght | scaleX | track | use |
|
|
50
|
+
|---|---|---|---|---|
|
|
51
|
+
| `.zen-air` | 220 | — | −.030em | thin display |
|
|
52
|
+
| `.zen-book` | 497 | — | — | text |
|
|
53
|
+
| `.zen-medium` | 606 | — | — | UI emphasis |
|
|
54
|
+
| `.zen-wide` | 845 | 1.56 | −.040em | monumental caps |
|
|
55
|
+
| `.zen-round` | 900 | — | −.018em | heavy and friendly |
|
|
56
|
+
|
|
57
|
+
`.zen-wide` transforms, so its LAYOUT box stays the untransformed width — give
|
|
58
|
+
it room or clip its container.
|
|
59
|
+
|
|
60
|
+
**Zen's own alternates cover the neo-grotesque register**, so a brand that wants
|
|
61
|
+
that voice changes a feature setting rather than a family: `ss01` is the
|
|
62
|
+
single-storey `a`, `ss04` the straight-leg `R`, `ss09` a slashed zero and serif
|
|
63
|
+
one. Eleven sets ship.
|
|
54
64
|
|
|
55
65
|
---
|
|
56
66
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/design",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.19",
|
|
4
4
|
"packageManager": "pnpm@11.17.0",
|
|
5
5
|
"description": "Hanzo Design System \u2014 monochrome, dark-default tokens + components + brand assets, the single source of truth for every Hanzo surface. CSS + typed programmatic tokens.",
|
|
6
6
|
"license": "MIT OR Apache-2.0",
|
|
@@ -30,8 +30,7 @@
|
|
|
30
30
|
"./prompts/*": "./prompts/*",
|
|
31
31
|
"./content/*": "./content/*",
|
|
32
32
|
"./assets/*": "./assets/*",
|
|
33
|
-
"./skills/*": "./skills/*"
|
|
34
|
-
"./tailwind.css": "./tailwind.css"
|
|
33
|
+
"./skills/*": "./skills/*"
|
|
35
34
|
},
|
|
36
35
|
"scripts": {
|
|
37
36
|
"gen": "node scripts/gen-tokens.mjs",
|
|
@@ -11,8 +11,15 @@ You are the **Hanzo Design System designer**, an agent operating on the
|
|
|
11
11
|
system as real files, using your file tools (read, write, edit, tree).
|
|
12
12
|
|
|
13
13
|
## The canon (never contradict)
|
|
14
|
-
- **Typography:**
|
|
15
|
-
|
|
14
|
+
- **Typography:** Zen (`sans`) and Zen Mono (`mono`), both variable on `wght`
|
|
15
|
+
100–900, plus Zen Pixel for display accents. ONE family, ours, no licensed
|
|
16
|
+
third-party face and no Inter/Roboto/DM Sans. An app imports
|
|
17
|
+
`@hanzo/design/styles.css` and gets the faces with it — never hand-write an
|
|
18
|
+
`@font-face` and never reach for a font CDN. Name the role (`var(--font-sans)`),
|
|
19
|
+
never the face. Brand voice comes from a preset in `@hanzo/font/presets.css`
|
|
20
|
+
(`.zen-air` `.zen-book` `.zen-medium` `.zen-wide` `.zen-round`) or from Zen's own
|
|
21
|
+
stylistic sets (`ss01` single-storey a, `ss04` straight-leg R), never from a
|
|
22
|
+
second family.
|
|
16
23
|
- **Palette:** true-black OLED. Page `#000`, surface `#0a0a0a`, press `#050505`,
|
|
17
24
|
elevated `#171717`, border `rgba(255,255,255,.10)`, text `#ededf1`. **Monochrome —
|
|
18
25
|
no colored accents.** Semantic color only for live/error/warning.
|
package/scripts/check-tokens.mjs
CHANGED
|
@@ -60,7 +60,6 @@ const pass = (msg) => console.log(` ok ${msg}`)
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// ── 1a. the bundles must PARSE ───────────────────────────────────────────
|
|
63
|
-
// 0.4.2 and 0.4.3 shipped a stray `*/` in styles.css AND tailwind.css: an
|
|
64
63
|
// edit added prose to the end of a comment that was already closed, so a
|
|
65
64
|
// paragraph of English sat in the stylesheet as raw CSS, terminated by a
|
|
66
65
|
// second `*/`. Browsers recover from that — they skip to the next thing that
|
|
@@ -78,7 +77,7 @@ const pass = (msg) => console.log(` ok ${msg}`)
|
|
|
78
77
|
// that skips comment bodies finds every stray terminator and every unclosed
|
|
79
78
|
// opener. A parser dependency would be a heavier answer to a smaller question.
|
|
80
79
|
{
|
|
81
|
-
for (const f of ['styles.css'
|
|
80
|
+
for (const f of ['styles.css']) {
|
|
82
81
|
const s = read(join(root, f))
|
|
83
82
|
let i = 0, opened = 0, strays = [], unterminated = null
|
|
84
83
|
while (i < s.length - 1) {
|
|
@@ -100,31 +99,11 @@ const pass = (msg) => console.log(` ok ${msg}`)
|
|
|
100
99
|
}
|
|
101
100
|
}
|
|
102
101
|
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// every token without a word.
|
|
109
|
-
{
|
|
110
|
-
const tw = strip(read(join(root, 'tailwind.css')))
|
|
111
|
-
tw.includes('@import')
|
|
112
|
-
? fail('tailwind.css contains an @import — invalid after `@import "tailwindcss"`, so the tokens are dropped')
|
|
113
|
-
: pass('tailwind.css has no @import to invalidate')
|
|
114
|
-
|
|
115
|
-
// Every Tailwind colour slot an app will reach for must be mapped.
|
|
116
|
-
const slots = ['background', 'foreground', 'card', 'popover', 'primary', 'secondary',
|
|
117
|
-
'muted', 'muted-foreground', 'accent', 'destructive', 'border', 'input', 'ring']
|
|
118
|
-
const unmapped = slots.filter((s) => !tw.includes(`--color-${s}:`))
|
|
119
|
-
unmapped.length
|
|
120
|
-
? fail(`tailwind.css does not map: ${unmapped.join(', ')}`)
|
|
121
|
-
: pass(`tailwind.css maps all ${slots.length} core colour slots`)
|
|
122
|
-
|
|
123
|
-
// And it must carry the values, not merely reference them.
|
|
124
|
-
tw.includes('--border:')
|
|
125
|
-
? pass('tailwind.css carries the token values inline')
|
|
126
|
-
: fail('tailwind.css maps slots but carries no tokens — every utility resolves to nothing')
|
|
127
|
-
}
|
|
102
|
+
// 1b was the Tailwind bridge — a generated `tailwind.css` mapping every token
|
|
103
|
+
// onto a `--color-*` utility slot. Gone with the utility layer it existed to
|
|
104
|
+
// feed: this system's one substrate is @hanzo/gui, which reads the tokens
|
|
105
|
+
// directly, so a second sheet republishing them as utilities was the "one fact,
|
|
106
|
+
// two homes" shape the `second-publisher` lint rule exists to catch.
|
|
128
107
|
|
|
129
108
|
// ── 1c. element defaults must LOSE to an app's utilities ─────────────────
|
|
130
109
|
// A rule outside a cascade layer beats a rule inside one regardless of
|
package/scripts/gen-tokens.mjs
CHANGED
|
@@ -185,81 +185,3 @@ const bundle = banner + FILES.map((f) => {
|
|
|
185
185
|
writeFileSync(join(root, 'styles.css'), bundle)
|
|
186
186
|
console.log(`gen-tokens: wrote styles.css — ${FILES.length} token groups, flattened`)
|
|
187
187
|
|
|
188
|
-
// ── tailwind.css — the ONE line a Tailwind app writes ────────────────────
|
|
189
|
-
// Every app that consumed these tokens still had to hand-write the bridge from
|
|
190
|
-
// our variable names to Tailwind's utility namespace:
|
|
191
|
-
//
|
|
192
|
-
// @theme inline { --color-background: var(--background); ...twenty more }
|
|
193
|
-
//
|
|
194
|
-
// Twenty lines of boilerplate, copied into each new app, is a drift vector with
|
|
195
|
-
// a silent failure mode: miss one and that utility resolves to nothing —
|
|
196
|
-
// `border-border` becomes currentColor and paints a white hairline on black,
|
|
197
|
-
// which reads as a design choice rather than a missing line. Tabs shipped with
|
|
198
|
-
// three of these and used forty-one.
|
|
199
|
-
//
|
|
200
|
-
// So the bridge is generated here from SLOTS, and an app writes one import
|
|
201
|
-
// after `@import "tailwindcss"`. There is nothing left to get wrong, and adding
|
|
202
|
-
// a semantic token wires it up for every app at once.
|
|
203
|
-
const SLOTS = [
|
|
204
|
-
'background', 'foreground',
|
|
205
|
-
'card', 'card-foreground',
|
|
206
|
-
'popover', 'popover-foreground',
|
|
207
|
-
'primary', 'primary-foreground',
|
|
208
|
-
'secondary', 'secondary-foreground',
|
|
209
|
-
'muted', 'muted-foreground',
|
|
210
|
-
'accent', 'accent-foreground',
|
|
211
|
-
'destructive', 'destructive-foreground',
|
|
212
|
-
'border', 'input', 'ring',
|
|
213
|
-
]
|
|
214
|
-
// State colours are their own vocabulary and keep their names, so `text-error`
|
|
215
|
-
// and `bg-online` mean what they say instead of borrowing a neutral slot.
|
|
216
|
-
const STATE = { error: 'state-error', success: 'state-success', online: 'state-online' }
|
|
217
|
-
|
|
218
|
-
const declared = new Set(flat.map(([n]) => n.replace(/^--/, '')))
|
|
219
|
-
const missing = SLOTS.filter((s) => !declared.has(s))
|
|
220
|
-
if (missing.length) {
|
|
221
|
-
console.error(`gen-tokens: tokens/colors.css declares no --${missing.join(', --')}`)
|
|
222
|
-
process.exit(1)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const tw = `/* Hanzo Design System — Tailwind v4 bridge. The ONE import a Tailwind app adds.
|
|
226
|
-
*
|
|
227
|
-
* AUTO-GENERATED by scripts/gen-tokens.mjs — DO NOT EDIT.
|
|
228
|
-
*
|
|
229
|
-
* @import "tailwindcss";
|
|
230
|
-
* @import "@hanzo/design/tailwind.css";
|
|
231
|
-
*
|
|
232
|
-
* It carries the tokens AND maps them onto Tailwind's utility namespace, so
|
|
233
|
-
* \`bg-background\`, \`text-muted-foreground\` and \`border-border\` resolve to the
|
|
234
|
-
* system's values with nothing to wire up by hand.
|
|
235
|
-
*
|
|
236
|
-
* Import it AFTER tailwindcss: @theme has to come after the framework it
|
|
237
|
-
* extends, and the element defaults are layered so your utilities still win.
|
|
238
|
-
*
|
|
239
|
-
* "inline" is deliberate — it compiles \`border-border\` to var(--border) rather
|
|
240
|
-
* than to that variable's build-time value, so switching .light at runtime moves
|
|
241
|
-
* the UI and not merely the variables.
|
|
242
|
-
*
|
|
243
|
-
* The tokens are inlined below rather than @imported for the reason styles.css
|
|
244
|
-
* is flattened: an @import must precede every other rule, so importing this
|
|
245
|
-
* after tailwindcss would invalidate it and the browser would drop the whole
|
|
246
|
-
* token layer without a word.
|
|
247
|
-
*/
|
|
248
|
-
${bundle}
|
|
249
|
-
|
|
250
|
-
@theme inline {
|
|
251
|
-
${SLOTS.map((s) => ` --color-${s}: var(--${s});`).join('\n')}
|
|
252
|
-
|
|
253
|
-
${Object.entries(STATE).map(([k, v]) => ` --color-${k}: var(--${v});`).join('\n')}
|
|
254
|
-
|
|
255
|
-
--radius-sm: var(--radius-sm);
|
|
256
|
-
--radius-md: var(--radius-md);
|
|
257
|
-
--radius-lg: var(--radius-lg);
|
|
258
|
-
--radius-xl: var(--radius-xl);
|
|
259
|
-
|
|
260
|
-
--font-sans: var(--font-sans);
|
|
261
|
-
--font-mono: var(--font-mono);
|
|
262
|
-
}
|
|
263
|
-
`
|
|
264
|
-
writeFileSync(join(root, 'tailwind.css'), tw)
|
|
265
|
-
console.log(`gen-tokens: wrote tailwind.css — ${SLOTS.length} colour slots + ${Object.keys(STATE).length} states`)
|
package/src/tokens.gen.ts
CHANGED
|
@@ -309,9 +309,9 @@ export const zIndex = {
|
|
|
309
309
|
|
|
310
310
|
/** fonts tokens (from tokens/fonts.css). Values are raw CSS. */
|
|
311
311
|
export const fonts = {
|
|
312
|
-
'font-sans': '"
|
|
312
|
+
'font-sans': '"Zen",ui-sans-serif,system-ui,sans-serif',
|
|
313
313
|
'font-display': 'var(--font-sans)',
|
|
314
|
-
'font-mono': '"
|
|
314
|
+
'font-mono': '"Zen Mono",ui-monospace,SFMono-Regular,monospace',
|
|
315
315
|
'font-serif': 'Georgia,serif',
|
|
316
316
|
'font-feature-settings': '"ss01","ss02","cv01","cv02","cv03"',
|
|
317
317
|
} as const
|
|
@@ -596,9 +596,9 @@ export const cssVars = {
|
|
|
596
596
|
'--z-toast': '800',
|
|
597
597
|
'--z-tooltip': 'var(--z-popover)',
|
|
598
598
|
'--z-notification': 'var(--z-toast)',
|
|
599
|
-
'--font-sans': '"
|
|
599
|
+
'--font-sans': '"Zen",ui-sans-serif,system-ui,sans-serif',
|
|
600
600
|
'--font-display': 'var(--font-sans)',
|
|
601
|
-
'--font-mono': '"
|
|
601
|
+
'--font-mono': '"Zen Mono",ui-monospace,SFMono-Regular,monospace',
|
|
602
602
|
'--font-serif': 'Georgia,serif',
|
|
603
603
|
'--font-feature-settings': '"ss01","ss02","cv01","cv02","cv03"',
|
|
604
604
|
'--ring-offset': '-2px',
|
package/styles.css
CHANGED
|
@@ -838,44 +838,67 @@
|
|
|
838
838
|
}
|
|
839
839
|
|
|
840
840
|
/* ── tokens/fonts.css ─────────────────────────────────────── */
|
|
841
|
-
/*
|
|
841
|
+
/* Zen — the only family on Hanzo surfaces, sans and mono.
|
|
842
842
|
SELF-HOSTED. The faces ship inside this package (assets/fonts/*.woff2, two
|
|
843
|
-
variable files, 141 KB total, SIL OFL-1.1 — see assets/fonts/LICENSE-
|
|
844
|
-
|
|
845
|
-
|
|
843
|
+
variable files, 141 KB total, SIL OFL-1.1 — see assets/fonts/LICENSE-Zen.txt).
|
|
844
|
+
|
|
845
|
+
Zen is our own family, under the SIL Open Font License. The licence file
|
|
846
|
+
beside the binaries carries every copyright the OFL requires; nothing else in
|
|
847
|
+
this repository restates them, which is the whole of what §2 asks for.
|
|
848
|
+
|
|
849
|
+
SHIPPED here rather than @import-ed from `@hanzo/font`, and that is load
|
|
850
|
+
bearing. `@hanzo/ui` composes this file into the MIDDLE of its theme.css, and
|
|
851
|
+
CSS drops an @import that does not precede every other rule — measured, in
|
|
852
|
+
Chromium, on the built sheet: zero faces loaded and not one woff2 requested.
|
|
853
|
+
An @font-face has no such ordering rule, so declaring the faces is the only
|
|
854
|
+
form that survives being flattened into a larger stylesheet. `@hanzo/font`
|
|
855
|
+
remains the one place the faces are AUTHORED; this is a copy of its output,
|
|
856
|
+
the same way the colour tokens are copied rather than fetched.
|
|
857
|
+
|
|
858
|
+
Why self-hosted rather than a third-party CDN:
|
|
846
859
|
- A sign-in page must not make a third-party request. hanzoai/id refused to
|
|
847
860
|
import this file for exactly that reason, which split the token layer: id
|
|
848
861
|
took the colours and not the typeface. Self-hosting removes the reason, so
|
|
849
862
|
every surface can import styles.css unchanged.
|
|
850
|
-
-
|
|
851
|
-
|
|
863
|
+
- It was a render-blocking request to a host we do not control, on the
|
|
864
|
+
critical path of every surface, and it broke offline/air-gapped dev.
|
|
852
865
|
- One variable file per family replaces nine static weights, and it is fewer
|
|
853
|
-
bytes than the CSS-then-woff2 round trip
|
|
866
|
+
bytes than the CSS-then-woff2 round trip.
|
|
854
867
|
|
|
855
868
|
The url()s are relative to THIS file, so they resolve wherever the package is
|
|
856
|
-
mounted — node_modules, a CDN, a copied dist — with no configuration.
|
|
869
|
+
mounted — node_modules, a CDN, a copied dist — with no configuration.
|
|
870
|
+
|
|
871
|
+
`font-display: block`, NOT swap, and the difference is the whole brand. `swap`
|
|
872
|
+
paints the fallback IMMEDIATELY and repaints when the face lands, so every first
|
|
873
|
+
visit shows the platform's own sans for as long as the download takes — on Ubuntu
|
|
874
|
+
that is DejaVu, which looks nothing like us and is the single most common way a
|
|
875
|
+
Hanzo surface renders wrong. `block` holds the text invisible for a short period
|
|
876
|
+
(~3s) and then paints Zen, so the fallback is only ever reached when the face
|
|
877
|
+
genuinely fails to arrive. A held word is a moment; the wrong typeface is the
|
|
878
|
+
brand. The generic tail below stays for exactly that real-failure case. */
|
|
857
879
|
|
|
858
880
|
@font-face{
|
|
859
|
-
font-family:"
|
|
860
|
-
src:url("./assets/fonts/
|
|
881
|
+
font-family:"Zen";
|
|
882
|
+
src:url("./assets/fonts/Zen-Variable.woff2") format("woff2");
|
|
861
883
|
font-weight:100 900;
|
|
862
884
|
font-style:normal;
|
|
863
|
-
font-display:
|
|
885
|
+
font-display:block;
|
|
864
886
|
}
|
|
865
887
|
@font-face{
|
|
866
|
-
font-family:"
|
|
867
|
-
src:url("./assets/fonts/
|
|
888
|
+
font-family:"Zen Mono";
|
|
889
|
+
src:url("./assets/fonts/ZenMono-Variable.woff2") format("woff2");
|
|
868
890
|
font-weight:100 900;
|
|
869
891
|
font-style:normal;
|
|
870
|
-
font-display:
|
|
892
|
+
font-display:block;
|
|
871
893
|
}
|
|
872
894
|
|
|
873
895
|
:root{
|
|
874
|
-
--font-sans:"
|
|
896
|
+
--font-sans:"Zen",ui-sans-serif,system-ui,sans-serif;
|
|
875
897
|
--font-display:var(--font-sans);
|
|
876
|
-
--font-mono:"
|
|
898
|
+
--font-mono:"Zen Mono",ui-monospace,SFMono-Regular,monospace;
|
|
877
899
|
--font-serif:Georgia,serif;
|
|
878
|
-
/* hanzo.ai sets these OpenType features on <body>.
|
|
900
|
+
/* hanzo.ai sets these OpenType features on <body>. ss01 is Zen's
|
|
901
|
+
single-storey a — the neo-grotesque letterform. */
|
|
879
902
|
--font-feature-settings:"ss01","ss02","cv01","cv02","cv03";
|
|
880
903
|
}
|
|
881
904
|
|
package/tokens/fonts.css
CHANGED
|
@@ -1,40 +1,63 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* Zen — the only family on Hanzo surfaces, sans and mono.
|
|
2
2
|
SELF-HOSTED. The faces ship inside this package (assets/fonts/*.woff2, two
|
|
3
|
-
variable files, 141 KB total, SIL OFL-1.1 — see assets/fonts/LICENSE-
|
|
3
|
+
variable files, 141 KB total, SIL OFL-1.1 — see assets/fonts/LICENSE-Zen.txt).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Zen is our own family, under the SIL Open Font License. The licence file
|
|
6
|
+
beside the binaries carries every copyright the OFL requires; nothing else in
|
|
7
|
+
this repository restates them, which is the whole of what §2 asks for.
|
|
8
|
+
|
|
9
|
+
SHIPPED here rather than @import-ed from `@hanzo/font`, and that is load
|
|
10
|
+
bearing. `@hanzo/ui` composes this file into the MIDDLE of its theme.css, and
|
|
11
|
+
CSS drops an @import that does not precede every other rule — measured, in
|
|
12
|
+
Chromium, on the built sheet: zero faces loaded and not one woff2 requested.
|
|
13
|
+
An @font-face has no such ordering rule, so declaring the faces is the only
|
|
14
|
+
form that survives being flattened into a larger stylesheet. `@hanzo/font`
|
|
15
|
+
remains the one place the faces are AUTHORED; this is a copy of its output,
|
|
16
|
+
the same way the colour tokens are copied rather than fetched.
|
|
17
|
+
|
|
18
|
+
Why self-hosted rather than a third-party CDN:
|
|
6
19
|
- A sign-in page must not make a third-party request. hanzoai/id refused to
|
|
7
20
|
import this file for exactly that reason, which split the token layer: id
|
|
8
21
|
took the colours and not the typeface. Self-hosting removes the reason, so
|
|
9
22
|
every surface can import styles.css unchanged.
|
|
10
|
-
-
|
|
11
|
-
|
|
23
|
+
- It was a render-blocking request to a host we do not control, on the
|
|
24
|
+
critical path of every surface, and it broke offline/air-gapped dev.
|
|
12
25
|
- One variable file per family replaces nine static weights, and it is fewer
|
|
13
|
-
bytes than the CSS-then-woff2 round trip
|
|
26
|
+
bytes than the CSS-then-woff2 round trip.
|
|
14
27
|
|
|
15
28
|
The url()s are relative to THIS file, so they resolve wherever the package is
|
|
16
|
-
mounted — node_modules, a CDN, a copied dist — with no configuration.
|
|
29
|
+
mounted — node_modules, a CDN, a copied dist — with no configuration.
|
|
30
|
+
|
|
31
|
+
`font-display: block`, NOT swap, and the difference is the whole brand. `swap`
|
|
32
|
+
paints the fallback IMMEDIATELY and repaints when the face lands, so every first
|
|
33
|
+
visit shows the platform's own sans for as long as the download takes — on Ubuntu
|
|
34
|
+
that is DejaVu, which looks nothing like us and is the single most common way a
|
|
35
|
+
Hanzo surface renders wrong. `block` holds the text invisible for a short period
|
|
36
|
+
(~3s) and then paints Zen, so the fallback is only ever reached when the face
|
|
37
|
+
genuinely fails to arrive. A held word is a moment; the wrong typeface is the
|
|
38
|
+
brand. The generic tail below stays for exactly that real-failure case. */
|
|
17
39
|
|
|
18
40
|
@font-face{
|
|
19
|
-
font-family:"
|
|
20
|
-
src:url("../assets/fonts/
|
|
41
|
+
font-family:"Zen";
|
|
42
|
+
src:url("../assets/fonts/Zen-Variable.woff2") format("woff2");
|
|
21
43
|
font-weight:100 900;
|
|
22
44
|
font-style:normal;
|
|
23
|
-
font-display:
|
|
45
|
+
font-display:block;
|
|
24
46
|
}
|
|
25
47
|
@font-face{
|
|
26
|
-
font-family:"
|
|
27
|
-
src:url("../assets/fonts/
|
|
48
|
+
font-family:"Zen Mono";
|
|
49
|
+
src:url("../assets/fonts/ZenMono-Variable.woff2") format("woff2");
|
|
28
50
|
font-weight:100 900;
|
|
29
51
|
font-style:normal;
|
|
30
|
-
font-display:
|
|
52
|
+
font-display:block;
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
:root{
|
|
34
|
-
--font-sans:"
|
|
56
|
+
--font-sans:"Zen",ui-sans-serif,system-ui,sans-serif;
|
|
35
57
|
--font-display:var(--font-sans);
|
|
36
|
-
--font-mono:"
|
|
58
|
+
--font-mono:"Zen Mono",ui-monospace,SFMono-Regular,monospace;
|
|
37
59
|
--font-serif:Georgia,serif;
|
|
38
|
-
/* hanzo.ai sets these OpenType features on <body>.
|
|
60
|
+
/* hanzo.ai sets these OpenType features on <body>. ss01 is Zen's
|
|
61
|
+
single-storey a — the neo-grotesque letterform. */
|
|
39
62
|
--font-feature-settings:"ss01","ss02","cv01","cv02","cv03";
|
|
40
63
|
}
|
|
Binary file
|
|
Binary file
|