@hanzo/design 0.1.0 → 0.3.0

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.
@@ -0,0 +1,41 @@
1
+ # Hanzo Studio — Design System Designer (agentic prompt)
2
+
3
+ The system prompt the Studio "Design System" designer hands to the Hanzo agent
4
+ stack. The agent generates/edits the design-system file tree in **hanzoai/design**
5
+ using its file tools (write/edit/tree), grounded in the canon.
6
+
7
+ ---
8
+
9
+ You are the **Hanzo Design System designer**, an agent operating on the
10
+ `hanzoai/design` repository. You produce and maintain a complete, coherent design
11
+ system as real files, using your file tools (read, write, edit, tree).
12
+
13
+ ## The canon (never contradict)
14
+ - **Typography:** Basel Grotesk (`sans`, weights 400 Book / 500 Medium, self-hosted
15
+ woff2) + Geist Mono (`mono`). No Inter/Roboto/DM Sans as defaults.
16
+ - **Palette:** true-black OLED. Page `#000`, surface `#0a0a0a`, press `#050505`,
17
+ elevated `#171717`, border `rgba(255,255,255,.10)`, text `#ededf1`. **Monochrome —
18
+ no colored accents.** Semantic color only for live/error/warning.
19
+ - **Sidebar:** 256px expanded / 48px rail; lucide `PanelLeft` toggle; `white/5` hover,
20
+ `white/10` active.
21
+ - Full source: `guidelines/DESIGN.md`.
22
+
23
+ ## Structure you own
24
+ `tokens/` (base·colors·fonts·spacing·radius·elevation·motion.css + TS modules) ·
25
+ `guidelines/` · `assets/{brand,logos,providers,fonts}` · `components/` · `ui_kits/`.
26
+
27
+ ## How you work
28
+ 1. **Read before writing.** `tree` the repo + read `guidelines/DESIGN.md` and the
29
+ relevant `tokens/*` so every new value stays consistent with what exists.
30
+ 2. **Tokens are the source.** A component reads token vars (`var(--hz-*)`), never a
31
+ hardcoded hex/px. If a needed token is missing, add it to `tokens/` first, then use it.
32
+ 3. **One and one way.** One place per value. Don't duplicate a color/space/font.
33
+ 4. **Generate real, coherent files** — a request like "add a Card component" writes
34
+ `components/card.{css,tsx,md}` referencing the tokens + a usage note, not a mock.
35
+ 5. **Honor the request scope.** "Generate a design system" → the full tree from the
36
+ canon. "Add elevation tokens" → just `tokens/elevation.css` + wire into `base.css`.
37
+ 6. **Never fabricate brand assets.** Reference real files in `assets/`; if a logo/font
38
+ is missing, note it as a TODO, don't invent bytes.
39
+ 7. **Explain what you wrote** (files + why) so the review is legible.
40
+
41
+ Output: the files, written to the repo, plus a one-paragraph summary of the change.
package/src/index.ts ADDED
@@ -0,0 +1,64 @@
1
+ // @hanzo/design — the ONE programmatic control plane for Hanzo's look & feel.
2
+ //
3
+ // The look/feel is authored ONCE as CSS custom properties in tokens/*.css
4
+ // (monochrome, dark-default — "one hue through an opacity ladder"). This module
5
+ // exposes those exact tokens to code, generated from the CSS so the two can
6
+ // never drift. Change a token in the CSS → the stylesheet AND every code
7
+ // consumer (the @hanzogui/shell theme, Tamagui, any TS surface) update together.
8
+ //
9
+ // import '@hanzo/design/styles.css' // the CSS layer (unchanged)
10
+ // import { colors, spacing, radius, cssVar } from '@hanzo/design' // the code layer
11
+ //
12
+ export * from './tokens.gen.js'
13
+ import { cssVars, type CssVarName } from './tokens.gen.js'
14
+
15
+ /** A token name with the leading `--` omitted: `'background'` for `'--background'`. */
16
+ export type TokenName = CssVarName extends `--${infer N}` ? N : never
17
+
18
+ /**
19
+ * A `var(--name, <authored literal>)` reference to a token — the ONE way code
20
+ * should reach a token, so it resolves through the live CSS cascade (honoring
21
+ * the viewer's light/dark theme and any brand fork) rather than baking a value.
22
+ *
23
+ * background: cssVar('--background') // → "var(--background, #000000)"
24
+ * color: cssVar('foreground', '#fff')// → "var(--foreground, #fff)"
25
+ *
26
+ * The name is checked AGAINST THE STYLESHEET at compile time. That check used to
27
+ * be opted out of with `| (string & {})`, which is how `cssVar('surface-1')`
28
+ * shipped: the token did not exist, `var(--surface-1)` resolved to nothing, and
29
+ * a menu painted transparent with no error anywhere. An undefined custom
30
+ * property fails SILENTLY, so the type is the only place it can be caught.
31
+ *
32
+ * When no explicit fallback is given the token's own authored literal is used,
33
+ * so the reference still paints on a host that has not loaded the CSS layer.
34
+ */
35
+ export function cssVar(name: CssVarName | TokenName, fallback?: string): string {
36
+ const n = (name.startsWith('--') ? name : `--${name}`) as CssVarName
37
+ const lit = fallback ?? (cssVars as Record<string, string>)[n]
38
+ return lit ? `var(${n}, ${lit})` : `var(${n})`
39
+ }
40
+
41
+ /** The raw authored value of a token (the literal from the CSS), or `undefined`. */
42
+ export function tokenValue(name: CssVarName): string | undefined {
43
+ return (cssVars as Record<string, string>)[name]
44
+ }
45
+
46
+ /**
47
+ * Inject the design-system stylesheet from code (idempotent) for surfaces that
48
+ * cannot use a bundler CSS import (e.g. a runtime-mounted island). Prefer the
49
+ * static `import '@hanzo/design/styles.css'` where a bundler is available.
50
+ * No-op outside the browser.
51
+ *
52
+ * `href` is REQUIRED: this used to default to esm.sh, which silently made a
53
+ * third-party CDN the origin of the entire token layer for anyone who called it
54
+ * bare. Pass a URL you serve.
55
+ */
56
+ export function injectDesignCss(href: string): void {
57
+ if (typeof document === 'undefined') return
58
+ if (document.querySelector('link[data-hanzo-design]')) return
59
+ const l = document.createElement('link')
60
+ l.rel = 'stylesheet'
61
+ l.href = href
62
+ l.setAttribute('data-hanzo-design', '')
63
+ document.head.appendChild(l)
64
+ }
@@ -0,0 +1,486 @@
1
+ // AUTO-GENERATED by scripts/gen-tokens.mjs from tokens/*.css — DO NOT EDIT.
2
+ // Edit the token in tokens/<group>.css and re-run `npm run gen`.
3
+ /* eslint-disable */
4
+
5
+ /** colors tokens (from tokens/colors.css). Values are raw CSS. */
6
+ export const colors = {
7
+ 'neutral-50': '#FAFAFA',
8
+ 'neutral-100': '#F5F5F5',
9
+ 'neutral-200': '#E5E5E5',
10
+ 'neutral-300': '#D4D4D4',
11
+ 'neutral-400': '#A3A3A3',
12
+ 'neutral-500': '#737373',
13
+ 'neutral-600': '#525252',
14
+ 'neutral-700': '#404040',
15
+ 'neutral-800': '#262626',
16
+ 'neutral-900': '#171717',
17
+ 'neutral-950': '#0A0A0A',
18
+ 'pure-black': '#000000',
19
+ 'pure-white': '#FFFFFF',
20
+ 'hanzo-black': '#0A0A0B',
21
+ 'hanzo-white': '#FFFFFF',
22
+ 'white-05': 'rgb(255 255 255 / .05)',
23
+ 'white-10': 'rgb(255 255 255 / .10)',
24
+ 'white-15': 'rgb(255 255 255 / .15)',
25
+ 'white-20': 'rgb(255 255 255 / .20)',
26
+ 'white-30': 'rgb(255 255 255 / .30)',
27
+ 'white-40': 'rgb(255 255 255 / .40)',
28
+ 'white-60': 'rgb(255 255 255 / .60)',
29
+ 'white-80': 'rgb(255 255 255 / .80)',
30
+ 'background': '#000000',
31
+ 'foreground': '#ededed',
32
+ 'card': '#0a0a0a',
33
+ 'card-foreground': '#f5f5f5',
34
+ 'popover': '#0a0a0a',
35
+ 'popover-foreground': '#f5f5f5',
36
+ 'primary': '#ffffff',
37
+ 'primary-foreground': '#000000',
38
+ 'secondary': '#1a1a1a',
39
+ 'secondary-foreground': '#f5f5f5',
40
+ 'muted': '#101010',
41
+ 'muted-foreground': '#888888',
42
+ 'accent': '#1a1a1a',
43
+ 'accent-foreground': '#f5f5f5',
44
+ 'destructive': '#666666',
45
+ 'destructive-foreground': '#f5f5f5',
46
+ 'border': '#1f1f1f',
47
+ 'input': '#1f1f1f',
48
+ 'ring': 'var(--neutral-500)',
49
+ 'brand': '#e4e4e7',
50
+ 'brand-foreground': '#09090b',
51
+ 'brand-muted': '#a3a3a3',
52
+ 'black': '#000000',
53
+ 'white': '#f5f5f5',
54
+ 'surface-page': 'var(--background)',
55
+ 'surface-card': 'rgb(23 23 23 / .5)',
56
+ 'surface-card-emphasis': 'rgb(23 23 23 / .8)',
57
+ 'surface-card-quiet': 'rgb(23 23 23 / .4)',
58
+ 'surface-overlay': 'rgb(10 10 10 / .95)',
59
+ 'surface-header': 'rgb(0 0 0 / .7)',
60
+ 'border-hairline': 'var(--neutral-800)',
61
+ 'border-card': 'var(--white-10)',
62
+ 'border-strong': 'var(--neutral-500)',
63
+ 'surface-0': 'var(--background)',
64
+ 'surface-1': 'var(--card)',
65
+ 'surface-2': 'var(--muted)',
66
+ 'surface-3': 'var(--secondary)',
67
+ 'text-primary': 'var(--pure-white)',
68
+ 'text-secondary': 'var(--white-80)',
69
+ 'text-tertiary': 'var(--white-60)',
70
+ 'text-helper': 'var(--muted-foreground)',
71
+ 'text-disabled': 'var(--white-30)',
72
+ 'state-error': '#ef4444',
73
+ 'state-error-text': '#fca5a5',
74
+ 'state-error-bg': 'rgb(239 68 68 / .1)',
75
+ 'state-online': '#4ade80',
76
+ 'state-success': '#22c55e',
77
+ 'chrome-dot-red': 'rgb(239 68 68 / .6)',
78
+ 'chrome-dot-yellow': 'rgb(234 179 8 / .6)',
79
+ 'chrome-dot-green': 'rgb(34 197 94 / .6)',
80
+ } as const
81
+
82
+ /** typography tokens (from tokens/typography.css). Values are raw CSS. */
83
+ export const typography = {
84
+ 'text-xs': '0.6875rem',
85
+ 'leading-xs': '1rem',
86
+ 'text-sm': '0.8125rem',
87
+ 'leading-sm': '1.15rem',
88
+ 'text-base': '0.875rem',
89
+ 'leading-base': '1.35rem',
90
+ 'text-lg': '0.9375rem',
91
+ 'leading-lg': '1.4rem',
92
+ 'text-xl': '1.0625rem',
93
+ 'leading-xl': '1.55rem',
94
+ 'text-2xl': '1.3125rem',
95
+ 'leading-2xl': '1.7rem',
96
+ 'text-3xl': '1.625rem',
97
+ 'leading-3xl': '1.95rem',
98
+ 'text-4xl': '2rem',
99
+ 'leading-4xl': '2.25rem',
100
+ 'text-5xl': '2.5rem',
101
+ 'leading-5xl': '1.05',
102
+ 'text-6xl': '3.25rem',
103
+ 'leading-6xl': '1',
104
+ 'text-7xl': '4rem',
105
+ 'leading-7xl': '1',
106
+ 'text-8xl': '5.25rem',
107
+ 'leading-8xl': '1',
108
+ 'text-9xl': '7rem',
109
+ 'leading-9xl': '1',
110
+ 'font-size-xs': 'var(--text-xs)',
111
+ 'font-size-sm': 'var(--text-sm)',
112
+ 'font-size-base': 'var(--text-base)',
113
+ 'font-size-lg': 'var(--text-lg)',
114
+ 'font-size-xl': 'var(--text-xl)',
115
+ 'font-size-2xl': 'var(--text-2xl)',
116
+ 'font-size-3xl': 'var(--text-3xl)',
117
+ 'font-size-4xl': 'var(--text-4xl)',
118
+ 'font-size-5xl': 'var(--text-5xl)',
119
+ 'font-size-6xl': 'var(--text-6xl)',
120
+ 'font-size-7xl': 'var(--text-7xl)',
121
+ 'font-size-8xl': 'var(--text-8xl)',
122
+ 'font-size-9xl': 'var(--text-9xl)',
123
+ 'weight-normal': '400',
124
+ 'weight-medium': '500',
125
+ 'weight-semibold': '600',
126
+ 'weight-bold': '700',
127
+ 'tracking-tight': '-0.025em',
128
+ 'tracking-normal': '0em',
129
+ 'tracking-wide': '0.025em',
130
+ 'tracking-widest': '0.1em',
131
+ 'leading-none': '1',
132
+ 'leading-tight': '1.25',
133
+ 'leading-snug': '1.375',
134
+ 'leading-normal': '1.5',
135
+ 'leading-relaxed': '1.625',
136
+ 'leading-golden': '1.618',
137
+ 'type-hero': '600 var(--text-5xl)/1.05 var(--font-display)',
138
+ 'type-h2': '700 var(--text-4xl)/var(--leading-4xl) var(--font-display)',
139
+ 'type-h3': '600 var(--text-xl)/var(--leading-xl) var(--font-display)',
140
+ 'type-lead': '400 var(--text-lg)/var(--leading-relaxed) var(--font-sans)',
141
+ 'type-body': '400 var(--text-sm)/var(--leading-sm) var(--font-sans)',
142
+ 'type-caption': '400 var(--text-xs)/var(--leading-xs) var(--font-sans)',
143
+ 'type-code': '400 var(--text-sm)/var(--leading-relaxed) var(--font-mono)',
144
+ 'type-eyebrow': '600 0.625rem/1 var(--font-sans)',
145
+ } as const
146
+
147
+ /** spacing tokens (from tokens/spacing.css). Values are raw CSS. */
148
+ export const spacing = {
149
+ 'space-0': '0',
150
+ 'space-1': '0.25rem',
151
+ 'space-2': '0.5rem',
152
+ 'space-3': '0.75rem',
153
+ 'space-4': '1rem',
154
+ 'space-5': '1.25rem',
155
+ 'space-6': '1.5rem',
156
+ 'space-8': '2rem',
157
+ 'space-10': '2.5rem',
158
+ 'space-12': '3rem',
159
+ 'space-14': '3.5rem',
160
+ 'space-16': '4rem',
161
+ 'space-20': '5rem',
162
+ 'space-24': '6rem',
163
+ 'space-32': '8rem',
164
+ 'golden-1': '0.25rem',
165
+ 'golden-2': '0.405rem',
166
+ 'golden-3': '0.654rem',
167
+ 'golden-4': '1.059rem',
168
+ 'golden-5': '1.713rem',
169
+ 'golden-6': '2.772rem',
170
+ 'golden-7': '4.487rem',
171
+ 'golden-8': '7.26rem',
172
+ 'golden-9': '11.749rem',
173
+ 'golden-split': '38.2% 61.8%',
174
+ 'container-max': '80rem',
175
+ 'container-prose': '48rem',
176
+ 'container-wide': '72rem',
177
+ 'gutter': '1rem',
178
+ 'gutter-sm': '1.5rem',
179
+ 'gutter-lg': '2rem',
180
+ 'section-y': '4rem',
181
+ 'section-y-lg': '6rem',
182
+ 'hero-y': '5rem',
183
+ 'hero-y-lg': '8rem',
184
+ 'header-height': '4rem',
185
+ } as const
186
+
187
+ /** radius tokens (from tokens/radius.css). Values are raw CSS. */
188
+ export const radius = {
189
+ 'radius': '0.5rem',
190
+ 'radius-sm': '0.375rem',
191
+ 'radius-md': '0.5rem',
192
+ 'radius-lg': '0.75rem',
193
+ 'radius-xl': '1rem',
194
+ 'radius-2xl': '1.5rem',
195
+ 'radius-composer': '28px',
196
+ 'radius-full': '9999px',
197
+ } as const
198
+
199
+ /** elevation tokens (from tokens/elevation.css). Values are raw CSS. */
200
+ export const elevation = {
201
+ 'shadow-none': 'none',
202
+ 'shadow-floating': '0 25px 50px -12px rgb(0 0 0 / .25)',
203
+ 'shadow-inset-hairline': 'inset 0 0 0 1px var(--white-10)',
204
+ 'ring-focus': '0 0 0 2px var(--ring)',
205
+ 'shadow-sm': '0 1px 2px 0 rgb(0 0 0 / .40)',
206
+ 'shadow': '0 1px 3px 0 rgb(0 0 0 / .45), 0 1px 2px -1px rgb(0 0 0 / .45)',
207
+ 'shadow-md': '0 4px 6px -1px rgb(0 0 0 / .50), 0 2px 4px -2px rgb(0 0 0 / .50)',
208
+ 'shadow-lg': '0 10px 15px -3px rgb(0 0 0 / .55), 0 4px 6px -4px rgb(0 0 0 / .55)',
209
+ 'shadow-xl': '0 20px 25px -5px rgb(0 0 0 / .60), 0 8px 10px -6px rgb(0 0 0 / .60)',
210
+ 'shadow-2xl': 'var(--shadow-floating)',
211
+ 'glow-hero': 'radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%)',
212
+ 'glow-hero-blur': '120px',
213
+ 'sheen-card': 'radial-gradient(120% 120% at 80% 0%,rgb(255 255 255 / .08) 0%,transparent 55%)',
214
+ 'gradient-chrome': 'linear-gradient(to right,#ffffff,var(--white-80),var(--white-60))',
215
+ 'gradient-chrome-2': 'linear-gradient(to right,#ffffff,var(--neutral-500))',
216
+ 'gradient-protect': 'linear-gradient(to bottom,var(--white-10),transparent)',
217
+ } as const
218
+
219
+ /** motion tokens (from tokens/motion.css). Values are raw CSS. */
220
+ export const motion = {
221
+ 'duration-fast': '150ms',
222
+ 'duration-base': '300ms',
223
+ 'duration-slow': '400ms',
224
+ 'duration-slower': '500ms',
225
+ 'duration-glow': '9s',
226
+ 'ease-out': 'cubic-bezier(0,0,0.2,1)',
227
+ 'ease-in-out': 'cubic-bezier(0.4,0,0.2,1)',
228
+ 'stagger': '60ms',
229
+ 'entry-rise': '16px',
230
+ 'entry-rise-lg': '24px',
231
+ } as const
232
+
233
+ /** zIndex tokens (from tokens/z.css). Values are raw CSS. */
234
+ export const zIndex = {
235
+ 'z-base': '0',
236
+ 'z-raised': '10',
237
+ 'z-sticky': '200',
238
+ 'z-header': '300',
239
+ 'z-dropdown': '400',
240
+ 'z-overlay': '500',
241
+ 'z-modal': '600',
242
+ 'z-popover': '700',
243
+ 'z-toast': '800',
244
+ 'z-tooltip': 'var(--z-popover)',
245
+ 'z-notification': 'var(--z-toast)',
246
+ } as const
247
+
248
+ /** fonts tokens (from tokens/fonts.css). Values are raw CSS. */
249
+ export const fonts = {
250
+ 'font-sans': '"Geist","Geist Sans",ui-sans-serif,system-ui,sans-serif',
251
+ 'font-display': 'var(--font-sans)',
252
+ 'font-mono': '"Geist Mono",ui-monospace,SFMono-Regular,monospace',
253
+ 'font-serif': 'Georgia,serif',
254
+ 'font-feature-settings': '"ss01","ss02","cv01","cv02","cv03"',
255
+ } as const
256
+
257
+ /** base tokens (from tokens/base.css). Values are raw CSS. */
258
+ export const base = {
259
+
260
+ } as const
261
+
262
+ /** Every token, keyed by its literal CSS custom-property name ('--background', …). */
263
+ export const cssVars = {
264
+ '--neutral-50': '#FAFAFA',
265
+ '--neutral-100': '#F5F5F5',
266
+ '--neutral-200': '#E5E5E5',
267
+ '--neutral-300': '#D4D4D4',
268
+ '--neutral-400': '#A3A3A3',
269
+ '--neutral-500': '#737373',
270
+ '--neutral-600': '#525252',
271
+ '--neutral-700': '#404040',
272
+ '--neutral-800': '#262626',
273
+ '--neutral-900': '#171717',
274
+ '--neutral-950': '#0A0A0A',
275
+ '--pure-black': '#000000',
276
+ '--pure-white': '#FFFFFF',
277
+ '--hanzo-black': '#0A0A0B',
278
+ '--hanzo-white': '#FFFFFF',
279
+ '--white-05': 'rgb(255 255 255 / .05)',
280
+ '--white-10': 'rgb(255 255 255 / .10)',
281
+ '--white-15': 'rgb(255 255 255 / .15)',
282
+ '--white-20': 'rgb(255 255 255 / .20)',
283
+ '--white-30': 'rgb(255 255 255 / .30)',
284
+ '--white-40': 'rgb(255 255 255 / .40)',
285
+ '--white-60': 'rgb(255 255 255 / .60)',
286
+ '--white-80': 'rgb(255 255 255 / .80)',
287
+ '--background': '#000000',
288
+ '--foreground': '#ededed',
289
+ '--card': '#0a0a0a',
290
+ '--card-foreground': '#f5f5f5',
291
+ '--popover': '#0a0a0a',
292
+ '--popover-foreground': '#f5f5f5',
293
+ '--primary': '#ffffff',
294
+ '--primary-foreground': '#000000',
295
+ '--secondary': '#1a1a1a',
296
+ '--secondary-foreground': '#f5f5f5',
297
+ '--muted': '#101010',
298
+ '--muted-foreground': '#888888',
299
+ '--accent': '#1a1a1a',
300
+ '--accent-foreground': '#f5f5f5',
301
+ '--destructive': '#666666',
302
+ '--destructive-foreground': '#f5f5f5',
303
+ '--border': '#1f1f1f',
304
+ '--input': '#1f1f1f',
305
+ '--ring': 'var(--neutral-500)',
306
+ '--brand': '#e4e4e7',
307
+ '--brand-foreground': '#09090b',
308
+ '--brand-muted': '#a3a3a3',
309
+ '--black': '#000000',
310
+ '--white': '#f5f5f5',
311
+ '--surface-page': 'var(--background)',
312
+ '--surface-card': 'rgb(23 23 23 / .5)',
313
+ '--surface-card-emphasis': 'rgb(23 23 23 / .8)',
314
+ '--surface-card-quiet': 'rgb(23 23 23 / .4)',
315
+ '--surface-overlay': 'rgb(10 10 10 / .95)',
316
+ '--surface-header': 'rgb(0 0 0 / .7)',
317
+ '--border-hairline': 'var(--neutral-800)',
318
+ '--border-card': 'var(--white-10)',
319
+ '--border-strong': 'var(--neutral-500)',
320
+ '--surface-0': 'var(--background)',
321
+ '--surface-1': 'var(--card)',
322
+ '--surface-2': 'var(--muted)',
323
+ '--surface-3': 'var(--secondary)',
324
+ '--text-primary': 'var(--pure-white)',
325
+ '--text-secondary': 'var(--white-80)',
326
+ '--text-tertiary': 'var(--white-60)',
327
+ '--text-helper': 'var(--muted-foreground)',
328
+ '--text-disabled': 'var(--white-30)',
329
+ '--state-error': '#ef4444',
330
+ '--state-error-text': '#fca5a5',
331
+ '--state-error-bg': 'rgb(239 68 68 / .1)',
332
+ '--state-online': '#4ade80',
333
+ '--state-success': '#22c55e',
334
+ '--chrome-dot-red': 'rgb(239 68 68 / .6)',
335
+ '--chrome-dot-yellow': 'rgb(234 179 8 / .6)',
336
+ '--chrome-dot-green': 'rgb(34 197 94 / .6)',
337
+ '--text-xs': '0.6875rem',
338
+ '--leading-xs': '1rem',
339
+ '--text-sm': '0.8125rem',
340
+ '--leading-sm': '1.15rem',
341
+ '--text-base': '0.875rem',
342
+ '--leading-base': '1.35rem',
343
+ '--text-lg': '0.9375rem',
344
+ '--leading-lg': '1.4rem',
345
+ '--text-xl': '1.0625rem',
346
+ '--leading-xl': '1.55rem',
347
+ '--text-2xl': '1.3125rem',
348
+ '--leading-2xl': '1.7rem',
349
+ '--text-3xl': '1.625rem',
350
+ '--leading-3xl': '1.95rem',
351
+ '--text-4xl': '2rem',
352
+ '--leading-4xl': '2.25rem',
353
+ '--text-5xl': '2.5rem',
354
+ '--leading-5xl': '1.05',
355
+ '--text-6xl': '3.25rem',
356
+ '--leading-6xl': '1',
357
+ '--text-7xl': '4rem',
358
+ '--leading-7xl': '1',
359
+ '--text-8xl': '5.25rem',
360
+ '--leading-8xl': '1',
361
+ '--text-9xl': '7rem',
362
+ '--leading-9xl': '1',
363
+ '--font-size-xs': 'var(--text-xs)',
364
+ '--font-size-sm': 'var(--text-sm)',
365
+ '--font-size-base': 'var(--text-base)',
366
+ '--font-size-lg': 'var(--text-lg)',
367
+ '--font-size-xl': 'var(--text-xl)',
368
+ '--font-size-2xl': 'var(--text-2xl)',
369
+ '--font-size-3xl': 'var(--text-3xl)',
370
+ '--font-size-4xl': 'var(--text-4xl)',
371
+ '--font-size-5xl': 'var(--text-5xl)',
372
+ '--font-size-6xl': 'var(--text-6xl)',
373
+ '--font-size-7xl': 'var(--text-7xl)',
374
+ '--font-size-8xl': 'var(--text-8xl)',
375
+ '--font-size-9xl': 'var(--text-9xl)',
376
+ '--weight-normal': '400',
377
+ '--weight-medium': '500',
378
+ '--weight-semibold': '600',
379
+ '--weight-bold': '700',
380
+ '--tracking-tight': '-0.025em',
381
+ '--tracking-normal': '0em',
382
+ '--tracking-wide': '0.025em',
383
+ '--tracking-widest': '0.1em',
384
+ '--leading-none': '1',
385
+ '--leading-tight': '1.25',
386
+ '--leading-snug': '1.375',
387
+ '--leading-normal': '1.5',
388
+ '--leading-relaxed': '1.625',
389
+ '--leading-golden': '1.618',
390
+ '--type-hero': '600 var(--text-5xl)/1.05 var(--font-display)',
391
+ '--type-h2': '700 var(--text-4xl)/var(--leading-4xl) var(--font-display)',
392
+ '--type-h3': '600 var(--text-xl)/var(--leading-xl) var(--font-display)',
393
+ '--type-lead': '400 var(--text-lg)/var(--leading-relaxed) var(--font-sans)',
394
+ '--type-body': '400 var(--text-sm)/var(--leading-sm) var(--font-sans)',
395
+ '--type-caption': '400 var(--text-xs)/var(--leading-xs) var(--font-sans)',
396
+ '--type-code': '400 var(--text-sm)/var(--leading-relaxed) var(--font-mono)',
397
+ '--type-eyebrow': '600 0.625rem/1 var(--font-sans)',
398
+ '--space-0': '0',
399
+ '--space-1': '0.25rem',
400
+ '--space-2': '0.5rem',
401
+ '--space-3': '0.75rem',
402
+ '--space-4': '1rem',
403
+ '--space-5': '1.25rem',
404
+ '--space-6': '1.5rem',
405
+ '--space-8': '2rem',
406
+ '--space-10': '2.5rem',
407
+ '--space-12': '3rem',
408
+ '--space-14': '3.5rem',
409
+ '--space-16': '4rem',
410
+ '--space-20': '5rem',
411
+ '--space-24': '6rem',
412
+ '--space-32': '8rem',
413
+ '--golden-1': '0.25rem',
414
+ '--golden-2': '0.405rem',
415
+ '--golden-3': '0.654rem',
416
+ '--golden-4': '1.059rem',
417
+ '--golden-5': '1.713rem',
418
+ '--golden-6': '2.772rem',
419
+ '--golden-7': '4.487rem',
420
+ '--golden-8': '7.26rem',
421
+ '--golden-9': '11.749rem',
422
+ '--golden-split': '38.2% 61.8%',
423
+ '--container-max': '80rem',
424
+ '--container-prose': '48rem',
425
+ '--container-wide': '72rem',
426
+ '--gutter': '1rem',
427
+ '--gutter-sm': '1.5rem',
428
+ '--gutter-lg': '2rem',
429
+ '--section-y': '4rem',
430
+ '--section-y-lg': '6rem',
431
+ '--hero-y': '5rem',
432
+ '--hero-y-lg': '8rem',
433
+ '--header-height': '4rem',
434
+ '--radius': '0.5rem',
435
+ '--radius-sm': '0.375rem',
436
+ '--radius-md': '0.5rem',
437
+ '--radius-lg': '0.75rem',
438
+ '--radius-xl': '1rem',
439
+ '--radius-2xl': '1.5rem',
440
+ '--radius-composer': '28px',
441
+ '--radius-full': '9999px',
442
+ '--shadow-none': 'none',
443
+ '--shadow-floating': '0 25px 50px -12px rgb(0 0 0 / .25)',
444
+ '--shadow-inset-hairline': 'inset 0 0 0 1px var(--white-10)',
445
+ '--ring-focus': '0 0 0 2px var(--ring)',
446
+ '--shadow-sm': '0 1px 2px 0 rgb(0 0 0 / .40)',
447
+ '--shadow': '0 1px 3px 0 rgb(0 0 0 / .45), 0 1px 2px -1px rgb(0 0 0 / .45)',
448
+ '--shadow-md': '0 4px 6px -1px rgb(0 0 0 / .50), 0 2px 4px -2px rgb(0 0 0 / .50)',
449
+ '--shadow-lg': '0 10px 15px -3px rgb(0 0 0 / .55), 0 4px 6px -4px rgb(0 0 0 / .55)',
450
+ '--shadow-xl': '0 20px 25px -5px rgb(0 0 0 / .60), 0 8px 10px -6px rgb(0 0 0 / .60)',
451
+ '--shadow-2xl': 'var(--shadow-floating)',
452
+ '--glow-hero': 'radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%)',
453
+ '--glow-hero-blur': '120px',
454
+ '--sheen-card': 'radial-gradient(120% 120% at 80% 0%,rgb(255 255 255 / .08) 0%,transparent 55%)',
455
+ '--gradient-chrome': 'linear-gradient(to right,#ffffff,var(--white-80),var(--white-60))',
456
+ '--gradient-chrome-2': 'linear-gradient(to right,#ffffff,var(--neutral-500))',
457
+ '--gradient-protect': 'linear-gradient(to bottom,var(--white-10),transparent)',
458
+ '--duration-fast': '150ms',
459
+ '--duration-base': '300ms',
460
+ '--duration-slow': '400ms',
461
+ '--duration-slower': '500ms',
462
+ '--duration-glow': '9s',
463
+ '--ease-out': 'cubic-bezier(0,0,0.2,1)',
464
+ '--ease-in-out': 'cubic-bezier(0.4,0,0.2,1)',
465
+ '--stagger': '60ms',
466
+ '--entry-rise': '16px',
467
+ '--entry-rise-lg': '24px',
468
+ '--z-base': '0',
469
+ '--z-raised': '10',
470
+ '--z-sticky': '200',
471
+ '--z-header': '300',
472
+ '--z-dropdown': '400',
473
+ '--z-overlay': '500',
474
+ '--z-modal': '600',
475
+ '--z-popover': '700',
476
+ '--z-toast': '800',
477
+ '--z-tooltip': 'var(--z-popover)',
478
+ '--z-notification': 'var(--z-toast)',
479
+ '--font-sans': '"Geist","Geist Sans",ui-sans-serif,system-ui,sans-serif',
480
+ '--font-display': 'var(--font-sans)',
481
+ '--font-mono': '"Geist Mono",ui-monospace,SFMono-Regular,monospace',
482
+ '--font-serif': 'Georgia,serif',
483
+ '--font-feature-settings': '"ss01","ss02","cv01","cv02","cv03"',
484
+ } as const
485
+
486
+ export type CssVarName = keyof typeof cssVars
package/styles.css CHANGED
@@ -1,5 +1,8 @@
1
1
  /* Hanzo Design System — global entry point. Import THIS one file.
2
- Source of truth: hanzo-apps/hanzo.ai (app/globals.css, tailwind.config.ts, DESIGN.md). */
2
+ Every token group is served here; nothing is authored but left unimported.
3
+ (`tokens/z.css` went missing from this list once and the whole ladder stopped
4
+ resolving while still type-checking — see scripts/check-tokens.mjs, which now
5
+ fails the build if any tokens/*.css is not imported below.) */
3
6
  @import url("tokens/fonts.css");
4
7
  @import url("tokens/colors.css");
5
8
  @import url("tokens/typography.css");
package/tokens/base.css CHANGED
@@ -2,10 +2,14 @@
2
2
  a utility framework. Components carry their own styles inline. */
3
3
  *{box-sizing:border-box;border-color:var(--border)}
4
4
  html{-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;scroll-behavior:smooth}
5
- body{margin:0;background:var(--background);color:var(--foreground);font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);font-feature-settings:var(--font-feature-settings)}
6
- h1,h2,h3,h4{margin:0;font-family:var(--font-display);letter-spacing:var(--tracking-tight);color:var(--text-primary)}
5
+ /* Each font-family carries the stack as a literal fallback. --font-sans lives in
6
+ tokens/fonts.css, which a surface may legitimately import separately; without
7
+ the fallback an unresolved var() makes font-family invalid and the UA drops to
8
+ its SERIF default — the whole console silently rendered in Times. */
9
+ body{margin:0;background:var(--background);color:var(--foreground);font-family:var(--font-sans,ui-sans-serif,system-ui,sans-serif);font-size:var(--text-base);line-height:var(--leading-base);font-feature-settings:var(--font-feature-settings)}
10
+ h1,h2,h3,h4{margin:0;font-family:var(--font-display,var(--font-sans,ui-sans-serif,system-ui,sans-serif));letter-spacing:var(--tracking-tight);color:var(--text-primary)}
7
11
  p{margin:0;text-wrap:pretty}
8
- code,pre,kbd{font-family:var(--font-mono)}
12
+ code,pre,kbd{font-family:var(--font-mono,ui-monospace,SFMono-Regular,monospace)}
9
13
  a{color:var(--text-primary);text-decoration:none;text-underline-offset:4px;transition:color var(--duration-fast) var(--ease-out)}
10
14
  a:hover{color:var(--text-primary);text-decoration:underline}
11
15
  :focus-visible{outline:2px solid var(--ring);outline-offset:2px}