@hanzo/design 0.2.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.
package/LICENSE CHANGED
@@ -1,21 +1,28 @@
1
- MIT License
1
+ BSD 3-Clause License
2
2
 
3
3
  Copyright (c) 2026 Hanzo AI, Inc.
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
11
7
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
14
10
 
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -12,12 +12,17 @@ The single source of truth for how every Hanzo surface looks — tokens, compone
12
12
 
13
13
  ## Use it
14
14
 
15
- One import pulls in the whole token layer (fonts, color, type, spacing, radius, elevation, motion):
15
+ One import pulls in the whole token layer fonts, colour, type, spacing, radius,
16
+ elevation, motion, **z**, base:
16
17
 
17
18
  ```css
18
19
  @import "@hanzo/design/styles.css";
19
20
  ```
20
21
 
22
+ Import the **whole** file. Cherry-picking `tokens/*.css` is how surfaces ended up
23
+ without the z ladder or the elevation ramp. The fonts are self-hosted, so this
24
+ makes **no third-party request** — there is no longer a reason to skip a part of it.
25
+
21
26
  ### Programmatic tokens (control look & feel from code)
22
27
 
23
28
  The same tokens are exposed to TypeScript — **generated from the CSS**, so the two
@@ -61,13 +66,42 @@ import { HanzoLogo } from "@hanzoai/design/components/core/HanzoLogo.jsx";
61
66
  | `content/` | The words — brand voice and taglines. |
62
67
  | `docs/` | How to use the system — integrate the tokens, theme, extend. |
63
68
  | `guidelines/` | Specimen cards — color, type, spacing, brand, iconography — the visual reference. |
69
+ | `assets/fonts/` | Geist Sans + Geist Mono, self-hosted (two variable `.woff2`, 141 KB, SIL OFL-1.1). No Google Fonts request. |
64
70
  | `assets/` | The mark, wordmark, favicon, provider + partner logos, brand imagery. |
71
+ | `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`. |
65
72
  | `ui_kits/` | Composed surfaces (e.g. `SiteChrome`) assembled from the components. |
66
73
 
74
+ ## Two kinds of boundary
75
+
76
+ Not interchangeable, and the difference is a conformance requirement rather than
77
+ a matter of taste:
78
+
79
+ | Token | Kind | Contrast |
80
+ |---|---|---|
81
+ | `--border`, `--border-hairline`, `--border-card` | **decorative** — separates content | none required |
82
+ | `--border-strong` | **perceivable** — identifies a *control* (input edge, switch, checkbox) | ≥ 3:1, WCAG 1.4.11 |
83
+ | `--ring` | the focus indicator | ≥ 3:1, WCAG 2.4.11 |
84
+
85
+ Reach for `--border-strong` whenever the boundary **is** the affordance. Both
86
+ conformant tokens resolve to `--neutral-500`, the only rung on this ladder that
87
+ clears 3:1 on every surface in **both** themes — `scripts/check-tokens.mjs`
88
+ measures this on every build and fails if it ever stops being true.
89
+
90
+ Note that the `--white-*` opacity ladder does **not** invert in light theme, so
91
+ `--white-40` is white-on-white there. Anything needing a visible edge in both
92
+ themes must use `--border-strong`.
93
+
67
94
  ## Principles
68
95
 
69
96
  - **Monochrome by construction** — one neutral ladder plus an opacity ladder is the entire palette. Color appears only as genuine semantics (live/error/warning).
70
97
  - **Dark is the default theme** — surfaces mount dark-first; light is the override.
98
+ - **A token that is referenced must resolve** — an undefined custom property paints
99
+ *nothing*, silently. `cssVar()` therefore accepts only real token names (a typo
100
+ is a compile error) and emits the authored literal as a fallback, so a reference
101
+ still paints on a host that has not loaded the CSS.
102
+ - **White-label by fork** — `@luxfi/design` and `@zooai/design` carry the same token
103
+ *names* over their own values. A change here must be measured against all three
104
+ before it ships; one that fixes Hanzo and breaks Zoo is a regression.
71
105
  - **Self-contained components** — inline styles, no CSS-framework coupling, so a component drops into any host (Next, Vite, Tamagui, none) and renders identically.
72
106
  - **Every component ships its own `.prompt.md`** — a one-screen usage guide for humans and AI alike.
73
107
 
@@ -77,4 +111,4 @@ Tokens track `hanzo.ai` (`app/globals.css`, `tailwind.config.ts`, `DESIGN.md`) a
77
111
 
78
112
  ## License
79
113
 
80
- MIT. Brand marks (the Hanzo logo, partner and provider logos) are the property of their respective owners and are provided for identification.
114
+ BSD-3-Clause. Brand marks (the Hanzo logo, partner and provider logos) are the property of their respective owners and are provided for identification.
@@ -0,0 +1,92 @@
1
+ Copyright (c) 2023 Vercel, in collaboration with basement.studio
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
+ http://scripts.sil.org/OFL
6
+
7
+ -----------------------------------------------------------
8
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9
+ -----------------------------------------------------------
10
+
11
+ PREAMBLE
12
+ The goals of the Open Font License (OFL) are to stimulate worldwide
13
+ development of collaborative font projects, to support the font creation
14
+ efforts of academic and linguistic communities, and to provide a free and
15
+ open framework in which fonts may be shared and improved in partnership
16
+ with others.
17
+
18
+ The OFL allows the licensed fonts to be used, studied, modified and
19
+ redistributed freely as long as they are not sold by themselves. The
20
+ fonts, including any derivative works, can be bundled, embedded,
21
+ redistributed and/or sold with any software provided that any reserved
22
+ names are not used by derivative works. The fonts and derivatives,
23
+ however, cannot be released under any other type of license. The
24
+ requirement for fonts to remain under this license does not apply
25
+ to any document created using the fonts or their derivatives.
26
+
27
+ DEFINITIONS
28
+ "Font Software" refers to the set of files released by the Copyright
29
+ Holder(s) under this license and clearly marked as such. This may
30
+ include source files, build scripts and documentation.
31
+
32
+ "Reserved Font Name" refers to any names specified as such after the
33
+ copyright statement(s).
34
+
35
+ "Original Version" refers to the collection of Font Software components as
36
+ distributed by the Copyright Holder(s).
37
+
38
+ "Modified Version" refers to any derivative made by adding to, deleting,
39
+ or substituting -- in part or in whole -- any of the components of the
40
+ Original Version, by changing formats or by porting the Font Software to a
41
+ new environment.
42
+
43
+ "Author" refers to any designer, engineer, programmer, technical
44
+ writer or other person who contributed to the Font Software.
45
+
46
+ PERMISSION AND CONDITIONS
47
+ Permission is hereby granted, free of charge, to any person obtaining
48
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
49
+ redistribute, and sell modified and unmodified copies of the Font
50
+ Software, subject to the following conditions:
51
+
52
+ 1) Neither the Font Software nor any of its individual components,
53
+ in Original or Modified Versions, may be sold by itself.
54
+
55
+ 2) Original or Modified Versions of the Font Software may be bundled,
56
+ redistributed and/or sold with any software, provided that each copy
57
+ contains the above copyright notice and this license. These can be
58
+ included either as stand-alone text files, human-readable headers or
59
+ in the appropriate machine-readable metadata fields within text or
60
+ binary files as long as those fields can be easily viewed by the user.
61
+
62
+ 3) No Modified Version of the Font Software may use the Reserved Font
63
+ Name(s) unless explicit written permission is granted by the corresponding
64
+ Copyright Holder. This restriction only applies to the primary font name as
65
+ presented to the users.
66
+
67
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
68
+ Software shall not be used to promote, endorse or advertise any
69
+ Modified Version, except to acknowledge the contribution(s) of the
70
+ Copyright Holder(s) and the Author(s) or with their explicit written
71
+ permission.
72
+
73
+ 5) The Font Software, modified or unmodified, in part or in whole,
74
+ must be distributed entirely under this license, and must not be
75
+ distributed under any other license. The requirement for fonts to
76
+ remain under this license does not apply to any document created
77
+ using the Font Software.
78
+
79
+ TERMINATION
80
+ This license becomes null and void if any of the above conditions are
81
+ not met.
82
+
83
+ DISCLAIMER
84
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
88
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92
+ OTHER DEALINGS IN THE FONT SOFTWARE.
package/dist/index.d.ts CHANGED
@@ -1,14 +1,25 @@
1
1
  export * from './tokens.gen.js';
2
2
  import { type CssVarName } from './tokens.gen.js';
3
+ /** A token name with the leading `--` omitted: `'background'` for `'--background'`. */
4
+ export type TokenName = CssVarName extends `--${infer N}` ? N : never;
3
5
  /**
4
- * A `var(--name[, fallback])` reference to a token — the ONE way code should
5
- * reach a token so it resolves through the live CSS cascade (honoring the
6
- * viewer's light/dark theme) rather than baking a fixed value.
6
+ * A `var(--name, <authored literal>)` reference to a token — the ONE way code
7
+ * should reach a token, so it resolves through the live CSS cascade (honoring
8
+ * the viewer's light/dark theme and any brand fork) rather than baking a value.
7
9
  *
8
- * background: cssVar('--background') // → "var(--background)"
9
- * color: cssVar('--foreground', '#fff')
10
+ * background: cssVar('--background') // → "var(--background, #000000)"
11
+ * color: cssVar('foreground', '#fff')// → "var(--foreground, #fff)"
12
+ *
13
+ * The name is checked AGAINST THE STYLESHEET at compile time. That check used to
14
+ * be opted out of with `| (string & {})`, which is how `cssVar('surface-1')`
15
+ * shipped: the token did not exist, `var(--surface-1)` resolved to nothing, and
16
+ * a menu painted transparent with no error anywhere. An undefined custom
17
+ * property fails SILENTLY, so the type is the only place it can be caught.
18
+ *
19
+ * When no explicit fallback is given the token's own authored literal is used,
20
+ * so the reference still paints on a host that has not loaded the CSS layer.
10
21
  */
11
- export declare function cssVar(name: CssVarName | (string & {}), fallback?: string): string;
22
+ export declare function cssVar(name: CssVarName | TokenName, fallback?: string): string;
12
23
  /** The raw authored value of a token (the literal from the CSS), or `undefined`. */
13
24
  export declare function tokenValue(name: CssVarName): string | undefined;
14
25
  /**
@@ -16,6 +27,10 @@ export declare function tokenValue(name: CssVarName): string | undefined;
16
27
  * cannot use a bundler CSS import (e.g. a runtime-mounted island). Prefer the
17
28
  * static `import '@hanzo/design/styles.css'` where a bundler is available.
18
29
  * No-op outside the browser.
30
+ *
31
+ * `href` is REQUIRED: this used to default to esm.sh, which silently made a
32
+ * third-party CDN the origin of the entire token layer for anyone who called it
33
+ * bare. Pass a URL you serve.
19
34
  */
20
- export declare function injectDesignCss(href?: string): void;
35
+ export declare function injectDesignCss(href: string): void;
21
36
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE1D;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAGlF;AAED,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,SAA4C,GAAG,IAAI,CAQtF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE1D,uFAAuF;AACvF,MAAM,MAAM,SAAS,GAAG,UAAU,SAAS,KAAK,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAA;AAErE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAI9E;AAED,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAE/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAQlD"}
package/dist/index.js CHANGED
@@ -12,16 +12,26 @@
12
12
  export * from './tokens.gen.js';
13
13
  import { cssVars } from './tokens.gen.js';
14
14
  /**
15
- * A `var(--name[, fallback])` reference to a token — the ONE way code should
16
- * reach a token so it resolves through the live CSS cascade (honoring the
17
- * viewer's light/dark theme) rather than baking a fixed value.
15
+ * A `var(--name, <authored literal>)` reference to a token — the ONE way code
16
+ * should reach a token, so it resolves through the live CSS cascade (honoring
17
+ * the viewer's light/dark theme and any brand fork) rather than baking a value.
18
18
  *
19
- * background: cssVar('--background') // → "var(--background)"
20
- * color: cssVar('--foreground', '#fff')
19
+ * background: cssVar('--background') // → "var(--background, #000000)"
20
+ * color: cssVar('foreground', '#fff')// → "var(--foreground, #fff)"
21
+ *
22
+ * The name is checked AGAINST THE STYLESHEET at compile time. That check used to
23
+ * be opted out of with `| (string & {})`, which is how `cssVar('surface-1')`
24
+ * shipped: the token did not exist, `var(--surface-1)` resolved to nothing, and
25
+ * a menu painted transparent with no error anywhere. An undefined custom
26
+ * property fails SILENTLY, so the type is the only place it can be caught.
27
+ *
28
+ * When no explicit fallback is given the token's own authored literal is used,
29
+ * so the reference still paints on a host that has not loaded the CSS layer.
21
30
  */
22
31
  export function cssVar(name, fallback) {
23
- const n = name.startsWith('--') ? name : `--${name}`;
24
- return fallback ? `var(${n}, ${fallback})` : `var(${n})`;
32
+ const n = (name.startsWith('--') ? name : `--${name}`);
33
+ const lit = fallback ?? cssVars[n];
34
+ return lit ? `var(${n}, ${lit})` : `var(${n})`;
25
35
  }
26
36
  /** The raw authored value of a token (the literal from the CSS), or `undefined`. */
27
37
  export function tokenValue(name) {
@@ -32,8 +42,12 @@ export function tokenValue(name) {
32
42
  * cannot use a bundler CSS import (e.g. a runtime-mounted island). Prefer the
33
43
  * static `import '@hanzo/design/styles.css'` where a bundler is available.
34
44
  * No-op outside the browser.
45
+ *
46
+ * `href` is REQUIRED: this used to default to esm.sh, which silently made a
47
+ * third-party CDN the origin of the entire token layer for anyone who called it
48
+ * bare. Pass a URL you serve.
35
49
  */
36
- export function injectDesignCss(href = 'https://esm.sh/@hanzo/design/styles.css') {
50
+ export function injectDesignCss(href) {
37
51
  if (typeof document === 'undefined')
38
52
  return;
39
53
  if (document.querySelector('link[data-hanzo-design]'))
@@ -41,7 +41,7 @@ export declare const colors: {
41
41
  readonly 'destructive-foreground': "#f5f5f5";
42
42
  readonly border: "#1f1f1f";
43
43
  readonly input: "#1f1f1f";
44
- readonly ring: "#333333";
44
+ readonly ring: "var(--neutral-500)";
45
45
  readonly brand: "#e4e4e7";
46
46
  readonly 'brand-foreground': "#09090b";
47
47
  readonly 'brand-muted': "#a3a3a3";
@@ -55,7 +55,11 @@ export declare const colors: {
55
55
  readonly 'surface-header': "rgb(0 0 0 / .7)";
56
56
  readonly 'border-hairline': "var(--neutral-800)";
57
57
  readonly 'border-card': "var(--white-10)";
58
- readonly 'border-strong': "var(--neutral-700)";
58
+ readonly 'border-strong': "var(--neutral-500)";
59
+ readonly 'surface-0': "var(--background)";
60
+ readonly 'surface-1': "var(--card)";
61
+ readonly 'surface-2': "var(--muted)";
62
+ readonly 'surface-3': "var(--secondary)";
59
63
  readonly 'text-primary': "var(--pure-white)";
60
64
  readonly 'text-secondary': "var(--white-80)";
61
65
  readonly 'text-tertiary': "var(--white-60)";
@@ -94,6 +98,23 @@ export declare const typography: {
94
98
  readonly 'leading-6xl': "1";
95
99
  readonly 'text-7xl': "4rem";
96
100
  readonly 'leading-7xl': "1";
101
+ readonly 'text-8xl': "5.25rem";
102
+ readonly 'leading-8xl': "1";
103
+ readonly 'text-9xl': "7rem";
104
+ readonly 'leading-9xl': "1";
105
+ readonly 'font-size-xs': "var(--text-xs)";
106
+ readonly 'font-size-sm': "var(--text-sm)";
107
+ readonly 'font-size-base': "var(--text-base)";
108
+ readonly 'font-size-lg': "var(--text-lg)";
109
+ readonly 'font-size-xl': "var(--text-xl)";
110
+ readonly 'font-size-2xl': "var(--text-2xl)";
111
+ readonly 'font-size-3xl': "var(--text-3xl)";
112
+ readonly 'font-size-4xl': "var(--text-4xl)";
113
+ readonly 'font-size-5xl': "var(--text-5xl)";
114
+ readonly 'font-size-6xl': "var(--text-6xl)";
115
+ readonly 'font-size-7xl': "var(--text-7xl)";
116
+ readonly 'font-size-8xl': "var(--text-8xl)";
117
+ readonly 'font-size-9xl': "var(--text-9xl)";
97
118
  readonly 'weight-normal': "400";
98
119
  readonly 'weight-medium': "500";
99
120
  readonly 'weight-semibold': "600";
@@ -173,6 +194,12 @@ export declare const elevation: {
173
194
  readonly 'shadow-floating': "0 25px 50px -12px rgb(0 0 0 / .25)";
174
195
  readonly 'shadow-inset-hairline': "inset 0 0 0 1px var(--white-10)";
175
196
  readonly 'ring-focus': "0 0 0 2px var(--ring)";
197
+ readonly 'shadow-sm': "0 1px 2px 0 rgb(0 0 0 / .40)";
198
+ readonly shadow: "0 1px 3px 0 rgb(0 0 0 / .45), 0 1px 2px -1px rgb(0 0 0 / .45)";
199
+ readonly 'shadow-md': "0 4px 6px -1px rgb(0 0 0 / .50), 0 2px 4px -2px rgb(0 0 0 / .50)";
200
+ readonly 'shadow-lg': "0 10px 15px -3px rgb(0 0 0 / .55), 0 4px 6px -4px rgb(0 0 0 / .55)";
201
+ readonly 'shadow-xl': "0 20px 25px -5px rgb(0 0 0 / .60), 0 8px 10px -6px rgb(0 0 0 / .60)";
202
+ readonly 'shadow-2xl': "var(--shadow-floating)";
176
203
  readonly 'glow-hero': "radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%)";
177
204
  readonly 'glow-hero-blur': "120px";
178
205
  readonly 'sheen-card': "radial-gradient(120% 120% at 80% 0%,rgb(255 255 255 / .08) 0%,transparent 55%)";
@@ -204,6 +231,8 @@ export declare const zIndex: {
204
231
  readonly 'z-modal': "600";
205
232
  readonly 'z-popover': "700";
206
233
  readonly 'z-toast': "800";
234
+ readonly 'z-tooltip': "var(--z-popover)";
235
+ readonly 'z-notification': "var(--z-toast)";
207
236
  };
208
237
  /** fonts tokens (from tokens/fonts.css). Values are raw CSS. */
209
238
  export declare const fonts: {
@@ -258,7 +287,7 @@ export declare const cssVars: {
258
287
  readonly '--destructive-foreground': "#f5f5f5";
259
288
  readonly '--border': "#1f1f1f";
260
289
  readonly '--input': "#1f1f1f";
261
- readonly '--ring': "#333333";
290
+ readonly '--ring': "var(--neutral-500)";
262
291
  readonly '--brand': "#e4e4e7";
263
292
  readonly '--brand-foreground': "#09090b";
264
293
  readonly '--brand-muted': "#a3a3a3";
@@ -272,7 +301,11 @@ export declare const cssVars: {
272
301
  readonly '--surface-header': "rgb(0 0 0 / .7)";
273
302
  readonly '--border-hairline': "var(--neutral-800)";
274
303
  readonly '--border-card': "var(--white-10)";
275
- readonly '--border-strong': "var(--neutral-700)";
304
+ readonly '--border-strong': "var(--neutral-500)";
305
+ readonly '--surface-0': "var(--background)";
306
+ readonly '--surface-1': "var(--card)";
307
+ readonly '--surface-2': "var(--muted)";
308
+ readonly '--surface-3': "var(--secondary)";
276
309
  readonly '--text-primary': "var(--pure-white)";
277
310
  readonly '--text-secondary': "var(--white-80)";
278
311
  readonly '--text-tertiary': "var(--white-60)";
@@ -308,6 +341,23 @@ export declare const cssVars: {
308
341
  readonly '--leading-6xl': "1";
309
342
  readonly '--text-7xl': "4rem";
310
343
  readonly '--leading-7xl': "1";
344
+ readonly '--text-8xl': "5.25rem";
345
+ readonly '--leading-8xl': "1";
346
+ readonly '--text-9xl': "7rem";
347
+ readonly '--leading-9xl': "1";
348
+ readonly '--font-size-xs': "var(--text-xs)";
349
+ readonly '--font-size-sm': "var(--text-sm)";
350
+ readonly '--font-size-base': "var(--text-base)";
351
+ readonly '--font-size-lg': "var(--text-lg)";
352
+ readonly '--font-size-xl': "var(--text-xl)";
353
+ readonly '--font-size-2xl': "var(--text-2xl)";
354
+ readonly '--font-size-3xl': "var(--text-3xl)";
355
+ readonly '--font-size-4xl': "var(--text-4xl)";
356
+ readonly '--font-size-5xl': "var(--text-5xl)";
357
+ readonly '--font-size-6xl': "var(--text-6xl)";
358
+ readonly '--font-size-7xl': "var(--text-7xl)";
359
+ readonly '--font-size-8xl': "var(--text-8xl)";
360
+ readonly '--font-size-9xl': "var(--text-9xl)";
311
361
  readonly '--weight-normal': "400";
312
362
  readonly '--weight-medium': "500";
313
363
  readonly '--weight-semibold': "600";
@@ -378,6 +428,12 @@ export declare const cssVars: {
378
428
  readonly '--shadow-floating': "0 25px 50px -12px rgb(0 0 0 / .25)";
379
429
  readonly '--shadow-inset-hairline': "inset 0 0 0 1px var(--white-10)";
380
430
  readonly '--ring-focus': "0 0 0 2px var(--ring)";
431
+ readonly '--shadow-sm': "0 1px 2px 0 rgb(0 0 0 / .40)";
432
+ readonly '--shadow': "0 1px 3px 0 rgb(0 0 0 / .45), 0 1px 2px -1px rgb(0 0 0 / .45)";
433
+ readonly '--shadow-md': "0 4px 6px -1px rgb(0 0 0 / .50), 0 2px 4px -2px rgb(0 0 0 / .50)";
434
+ readonly '--shadow-lg': "0 10px 15px -3px rgb(0 0 0 / .55), 0 4px 6px -4px rgb(0 0 0 / .55)";
435
+ readonly '--shadow-xl': "0 20px 25px -5px rgb(0 0 0 / .60), 0 8px 10px -6px rgb(0 0 0 / .60)";
436
+ readonly '--shadow-2xl': "var(--shadow-floating)";
381
437
  readonly '--glow-hero': "radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%)";
382
438
  readonly '--glow-hero-blur': "120px";
383
439
  readonly '--sheen-card': "radial-gradient(120% 120% at 80% 0%,rgb(255 255 255 / .08) 0%,transparent 55%)";
@@ -403,6 +459,8 @@ export declare const cssVars: {
403
459
  readonly '--z-modal': "600";
404
460
  readonly '--z-popover': "700";
405
461
  readonly '--z-toast': "800";
462
+ readonly '--z-tooltip': "var(--z-popover)";
463
+ readonly '--z-notification': "var(--z-toast)";
406
464
  readonly '--font-sans': "\"Geist\",\"Geist Sans\",ui-sans-serif,system-ui,sans-serif";
407
465
  readonly '--font-display': "var(--font-sans)";
408
466
  readonly '--font-mono': "\"Geist Mono\",ui-monospace,SFMono-Regular,monospace";
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.gen.d.ts","sourceRoot":"","sources":["../src/tokens.gen.ts"],"names":[],"mappings":"AAIA,kEAAkE;AAClE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsET,CAAA;AAEV,0EAA0E;AAC1E,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6Cb,CAAA;AAEV,oEAAoE;AACpE,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCV,CAAA;AAEV,kEAAkE;AAClE,eAAO,MAAM,MAAM;;;;;;;;;CAST,CAAA;AAEV,wEAAwE;AACxE,eAAO,MAAM,SAAS;;;;;;;;;;;CAWZ,CAAA;AAEV,kEAAkE;AAClE,eAAO,MAAM,MAAM;;;;;;;;;;;CAWT,CAAA;AAEV,6DAA6D;AAC7D,eAAO,MAAM,MAAM;;;;;;;;;;CAUT,CAAA;AAEV,gEAAgE;AAChE,eAAO,MAAM,KAAK;;;;;;CAMR,CAAA;AAEV,8DAA8D;AAC9D,eAAO,MAAM,IAAI,IAEP,CAAA;AAEV,sFAAsF;AACtF,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgMV,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,OAAO,CAAA"}
1
+ {"version":3,"file":"tokens.gen.d.ts","sourceRoot":"","sources":["../src/tokens.gen.ts"],"names":[],"mappings":"AAIA,kEAAkE;AAClE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0ET,CAAA;AAEV,0EAA0E;AAC1E,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8Db,CAAA;AAEV,oEAAoE;AACpE,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCV,CAAA;AAEV,kEAAkE;AAClE,eAAO,MAAM,MAAM;;;;;;;;;CAST,CAAA;AAEV,wEAAwE;AACxE,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;CAiBZ,CAAA;AAEV,kEAAkE;AAClE,eAAO,MAAM,MAAM;;;;;;;;;;;CAWT,CAAA;AAEV,6DAA6D;AAC7D,eAAO,MAAM,MAAM;;;;;;;;;;;;CAYT,CAAA;AAEV,gEAAgE;AAChE,eAAO,MAAM,KAAK;;;;;;CAMR,CAAA;AAEV,8DAA8D;AAC9D,eAAO,MAAM,IAAI,IAEP,CAAA;AAEV,sFAAsF;AACtF,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6NV,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,OAAO,CAAA"}
@@ -44,7 +44,7 @@ export const colors = {
44
44
  'destructive-foreground': '#f5f5f5',
45
45
  'border': '#1f1f1f',
46
46
  'input': '#1f1f1f',
47
- 'ring': '#333333',
47
+ 'ring': 'var(--neutral-500)',
48
48
  'brand': '#e4e4e7',
49
49
  'brand-foreground': '#09090b',
50
50
  'brand-muted': '#a3a3a3',
@@ -58,7 +58,11 @@ export const colors = {
58
58
  'surface-header': 'rgb(0 0 0 / .7)',
59
59
  'border-hairline': 'var(--neutral-800)',
60
60
  'border-card': 'var(--white-10)',
61
- 'border-strong': 'var(--neutral-700)',
61
+ 'border-strong': 'var(--neutral-500)',
62
+ 'surface-0': 'var(--background)',
63
+ 'surface-1': 'var(--card)',
64
+ 'surface-2': 'var(--muted)',
65
+ 'surface-3': 'var(--secondary)',
62
66
  'text-primary': 'var(--pure-white)',
63
67
  'text-secondary': 'var(--white-80)',
64
68
  'text-tertiary': 'var(--white-60)',
@@ -97,6 +101,23 @@ export const typography = {
97
101
  'leading-6xl': '1',
98
102
  'text-7xl': '4rem',
99
103
  'leading-7xl': '1',
104
+ 'text-8xl': '5.25rem',
105
+ 'leading-8xl': '1',
106
+ 'text-9xl': '7rem',
107
+ 'leading-9xl': '1',
108
+ 'font-size-xs': 'var(--text-xs)',
109
+ 'font-size-sm': 'var(--text-sm)',
110
+ 'font-size-base': 'var(--text-base)',
111
+ 'font-size-lg': 'var(--text-lg)',
112
+ 'font-size-xl': 'var(--text-xl)',
113
+ 'font-size-2xl': 'var(--text-2xl)',
114
+ 'font-size-3xl': 'var(--text-3xl)',
115
+ 'font-size-4xl': 'var(--text-4xl)',
116
+ 'font-size-5xl': 'var(--text-5xl)',
117
+ 'font-size-6xl': 'var(--text-6xl)',
118
+ 'font-size-7xl': 'var(--text-7xl)',
119
+ 'font-size-8xl': 'var(--text-8xl)',
120
+ 'font-size-9xl': 'var(--text-9xl)',
100
121
  'weight-normal': '400',
101
122
  'weight-medium': '500',
102
123
  'weight-semibold': '600',
@@ -176,6 +197,12 @@ export const elevation = {
176
197
  'shadow-floating': '0 25px 50px -12px rgb(0 0 0 / .25)',
177
198
  'shadow-inset-hairline': 'inset 0 0 0 1px var(--white-10)',
178
199
  'ring-focus': '0 0 0 2px var(--ring)',
200
+ 'shadow-sm': '0 1px 2px 0 rgb(0 0 0 / .40)',
201
+ 'shadow': '0 1px 3px 0 rgb(0 0 0 / .45), 0 1px 2px -1px rgb(0 0 0 / .45)',
202
+ 'shadow-md': '0 4px 6px -1px rgb(0 0 0 / .50), 0 2px 4px -2px rgb(0 0 0 / .50)',
203
+ 'shadow-lg': '0 10px 15px -3px rgb(0 0 0 / .55), 0 4px 6px -4px rgb(0 0 0 / .55)',
204
+ 'shadow-xl': '0 20px 25px -5px rgb(0 0 0 / .60), 0 8px 10px -6px rgb(0 0 0 / .60)',
205
+ 'shadow-2xl': 'var(--shadow-floating)',
179
206
  'glow-hero': 'radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%)',
180
207
  'glow-hero-blur': '120px',
181
208
  'sheen-card': 'radial-gradient(120% 120% at 80% 0%,rgb(255 255 255 / .08) 0%,transparent 55%)',
@@ -207,6 +234,8 @@ export const zIndex = {
207
234
  'z-modal': '600',
208
235
  'z-popover': '700',
209
236
  'z-toast': '800',
237
+ 'z-tooltip': 'var(--z-popover)',
238
+ 'z-notification': 'var(--z-toast)',
210
239
  };
211
240
  /** fonts tokens (from tokens/fonts.css). Values are raw CSS. */
212
241
  export const fonts = {
@@ -261,7 +290,7 @@ export const cssVars = {
261
290
  '--destructive-foreground': '#f5f5f5',
262
291
  '--border': '#1f1f1f',
263
292
  '--input': '#1f1f1f',
264
- '--ring': '#333333',
293
+ '--ring': 'var(--neutral-500)',
265
294
  '--brand': '#e4e4e7',
266
295
  '--brand-foreground': '#09090b',
267
296
  '--brand-muted': '#a3a3a3',
@@ -275,7 +304,11 @@ export const cssVars = {
275
304
  '--surface-header': 'rgb(0 0 0 / .7)',
276
305
  '--border-hairline': 'var(--neutral-800)',
277
306
  '--border-card': 'var(--white-10)',
278
- '--border-strong': 'var(--neutral-700)',
307
+ '--border-strong': 'var(--neutral-500)',
308
+ '--surface-0': 'var(--background)',
309
+ '--surface-1': 'var(--card)',
310
+ '--surface-2': 'var(--muted)',
311
+ '--surface-3': 'var(--secondary)',
279
312
  '--text-primary': 'var(--pure-white)',
280
313
  '--text-secondary': 'var(--white-80)',
281
314
  '--text-tertiary': 'var(--white-60)',
@@ -311,6 +344,23 @@ export const cssVars = {
311
344
  '--leading-6xl': '1',
312
345
  '--text-7xl': '4rem',
313
346
  '--leading-7xl': '1',
347
+ '--text-8xl': '5.25rem',
348
+ '--leading-8xl': '1',
349
+ '--text-9xl': '7rem',
350
+ '--leading-9xl': '1',
351
+ '--font-size-xs': 'var(--text-xs)',
352
+ '--font-size-sm': 'var(--text-sm)',
353
+ '--font-size-base': 'var(--text-base)',
354
+ '--font-size-lg': 'var(--text-lg)',
355
+ '--font-size-xl': 'var(--text-xl)',
356
+ '--font-size-2xl': 'var(--text-2xl)',
357
+ '--font-size-3xl': 'var(--text-3xl)',
358
+ '--font-size-4xl': 'var(--text-4xl)',
359
+ '--font-size-5xl': 'var(--text-5xl)',
360
+ '--font-size-6xl': 'var(--text-6xl)',
361
+ '--font-size-7xl': 'var(--text-7xl)',
362
+ '--font-size-8xl': 'var(--text-8xl)',
363
+ '--font-size-9xl': 'var(--text-9xl)',
314
364
  '--weight-normal': '400',
315
365
  '--weight-medium': '500',
316
366
  '--weight-semibold': '600',
@@ -381,6 +431,12 @@ export const cssVars = {
381
431
  '--shadow-floating': '0 25px 50px -12px rgb(0 0 0 / .25)',
382
432
  '--shadow-inset-hairline': 'inset 0 0 0 1px var(--white-10)',
383
433
  '--ring-focus': '0 0 0 2px var(--ring)',
434
+ '--shadow-sm': '0 1px 2px 0 rgb(0 0 0 / .40)',
435
+ '--shadow': '0 1px 3px 0 rgb(0 0 0 / .45), 0 1px 2px -1px rgb(0 0 0 / .45)',
436
+ '--shadow-md': '0 4px 6px -1px rgb(0 0 0 / .50), 0 2px 4px -2px rgb(0 0 0 / .50)',
437
+ '--shadow-lg': '0 10px 15px -3px rgb(0 0 0 / .55), 0 4px 6px -4px rgb(0 0 0 / .55)',
438
+ '--shadow-xl': '0 20px 25px -5px rgb(0 0 0 / .60), 0 8px 10px -6px rgb(0 0 0 / .60)',
439
+ '--shadow-2xl': 'var(--shadow-floating)',
384
440
  '--glow-hero': 'radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%)',
385
441
  '--glow-hero-blur': '120px',
386
442
  '--sheen-card': 'radial-gradient(120% 120% at 80% 0%,rgb(255 255 255 / .08) 0%,transparent 55%)',
@@ -406,6 +462,8 @@ export const cssVars = {
406
462
  '--z-modal': '600',
407
463
  '--z-popover': '700',
408
464
  '--z-toast': '800',
465
+ '--z-tooltip': 'var(--z-popover)',
466
+ '--z-notification': 'var(--z-toast)',
409
467
  '--font-sans': '"Geist","Geist Sans",ui-sans-serif,system-ui,sans-serif',
410
468
  '--font-display': 'var(--font-sans)',
411
469
  '--font-mono': '"Geist Mono",ui-monospace,SFMono-Regular,monospace',
@@ -0,0 +1,106 @@
1
+ # Hanzo Design System — Canonical Tokens
2
+
3
+ `@hanzo/ui` is the single source of truth for the shared Hanzo product look across
4
+ the **Tailwind** apps (hanzo.chat, hanzo.app, hanzo console, commerce, hanzo-desktop).
5
+ Change a value here; apps converge on it. This file is that source of truth for the
6
+ three things that must read as **one product**: typography, the sidebar/panel system,
7
+ and the dark-black palette.
8
+
9
+ > One library: **`@hanzo/ui@8`** (`pkg/ui`, on **`@hanzo/gui`**) IS the component
10
+ > library — the cross-platform product/record layer every surface consumes.
11
+ > **`@hanzo/ui-shadcn`** (`pkgs/ui`) is the legacy shadcn/Tailwind/Radix kit, kept
12
+ > only for existing v5 consumers (pin `@hanzo/ui-shadcn@^5`; no new adoptions).
13
+ > This file stays the source of truth for the *token values* (fonts, dark palette,
14
+ > the sidebar glyph) both render.
15
+
16
+ ---
17
+
18
+ ## 1. Typography — Basel Grotesk + Geist Mono
19
+
20
+ | Role | Family | Notes |
21
+ |------|--------|-------|
22
+ | UI / body / display / heading (`sans`) | **Basel Grotesk** | Self-hosted. Book = weight **400**, Medium = weight **500**. |
23
+ | code / data / mono (`mono`) | **Geist Mono** | `next/font/google` (`Geist_Mono`) or the geist CDN. |
24
+ | Arabic / Hebrew (`--font-ar` / `--font-he`) | unchanged | i18n only — keep. |
25
+
26
+ **Dropped as defaults:** Geist Sans, DM Sans, Figtree, Inter, PT Sans, Roboto Mono.
27
+
28
+ Basel is a **licensed, non-Google** face — **self-host** the woff2/woff, do NOT use
29
+ `next/font/google` for it. Canonical files (mirror lux.exchange):
30
+ `Basel-Grotesk-Book.woff2/.woff` (400), `Basel-Grotesk-Medium.woff2/.woff` (500).
31
+
32
+ `@font-face` (weights 400/500, `font-display: swap`, `font-style: normal`):
33
+
34
+ ```css
35
+ @font-face {
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
+ }
43
+ ```
44
+
45
+ Per-app adoption (converge the value, keep each app's own mechanism):
46
+ - **@hanzo/ui / Next apps** → `next/font/local` for Basel (`--font-basel-sans`) +
47
+ `next/font/google` `Geist_Mono` (`--font-geist-mono`). See `app/lib/fonts.ts`;
48
+ tailwind `sans → var(--font-basel-sans)`, `mono → var(--font-geist-mono)`.
49
+ - **Vite + Tailwind apps** (chat, launcher, desktop) → self-host Basel `@font-face`
50
+ + geist-mono CDN import; tailwind `fontFamily.sans = ['Basel', …]`,
51
+ `mono = ['Geist Mono', …]`.
52
+ - **Tamagui (console)** → Basel `@font-face` in globals + override the Tamagui
53
+ `body`/`heading` font `family` to Basel; Geist Mono for `code`/`pre`.
54
+
55
+ ---
56
+
57
+ ## 2. Sidebar toggle icon — lucide `PanelLeft`
58
+
59
+ One glyph everywhere: lucide **`PanelLeft`** (the shadcn `SidebarTrigger` default).
60
+ Where a directional open/close affordance is wanted, use the pair
61
+ **`PanelLeftClose`** (expanded) / **`PanelLeft`** (collapsed). Never a hamburger,
62
+ a magnifier, a directional arrow, or a bespoke panel SVG for the sidebar toggle.
63
+
64
+ - Icon: stroke-2, ~16–20px, `currentColor`.
65
+ - Button: ghost/outline, square (`size-7`/`h-6 w-6`), subtle hover.
66
+
67
+ ---
68
+
69
+ ## 3. Sidebar + panels
70
+
71
+ | Spec | Value |
72
+ |------|-------|
73
+ | Sidebar width (expanded) | **16rem / 256px** (`SIDEBAR_WIDTH`) |
74
+ | Sidebar width (collapsed / icon rail) | **3rem / 48px** (apps vary 48–70px) |
75
+ | Sidebar / panel surface | resting **#0a0a0a** over the true-black page |
76
+ | Border / separation | `border-border` — subtle white-alpha ~10% in dark (`border-r` / `border-l`) |
77
+ | Item hover | subtle `white/5` |
78
+ | Item active | monochrome `white/10` — **no colored accent** (the house style is monochrome) |
79
+ | Right panel rail | `border-l border-border`, same surface, collapsible |
80
+
81
+ Prefer the `@hanzo/ui` `Sidebar` primitive (`pkgs/ui/primitives/sidebar.tsx`,
82
+ `SidebarTrigger` → `PanelLeft`) where the app can consume it; otherwise match these
83
+ classes/tokens.
84
+
85
+ ---
86
+
87
+ ## 4. Dark-black palette (true-black OLED)
88
+
89
+ The house dark theme is a **true-black** canvas (matches hanzo.ai marketing +
90
+ hanzo.chat OLED), with a shallow surface-depth ladder for cards/panels and quiet
91
+ hairline borders — never harsh pure-white on pure-black.
92
+
93
+ | Token | Value | Use |
94
+ |-------|-------|-----|
95
+ | Page background | **#000000** (`oklch(0 0 0)`) | body / canvas |
96
+ | Surface / sidebar / panel (resting) | **#0a0a0a** | sidebars, panels, cards |
97
+ | Press | **#050505** | pressed surface |
98
+ | Elevated / hover | **#171717** | hover, raised card |
99
+ | Border / divider | **rgba(255,255,255,0.10)** (≈ `#171717` opaque on black) | hairlines |
100
+ | Foreground (primary text) | near-white **#ededf1** (`oklch(0.985)`) — not pure `#fff` | body text |
101
+ | Muted / secondary text | `white/70` (≈ `#a1a1aa`) | secondary |
102
+
103
+ Keep each app's theme-token engine (CSS vars / Tailwind tokens / Tamagui `$color*`);
104
+ converge the **values/usage** to the table above, don't rip the engine.
105
+
106
+ Reference: `pkgs/ui/style/hanzo-default-colors.css` (`.dark` / `.hanzo-ui-dark-theme`).
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@hanzo/design",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "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.",
5
- "license": "MIT",
5
+ "license": "BSD-3-Clause",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/hanzoai/design.git"
@@ -29,7 +29,8 @@
29
29
  },
30
30
  "scripts": {
31
31
  "gen": "node scripts/gen-tokens.mjs",
32
- "build": "npm run gen && tsc -p tsconfig.json",
32
+ "test": "node scripts/check-tokens.mjs",
33
+ "build": "npm run gen && npm test && tsc -p tsconfig.json",
33
34
  "prepublishOnly": "npm run build"
34
35
  },
35
36
  "devDependencies": {
package/prompts/README.md CHANGED
@@ -24,3 +24,10 @@ Everything here assumes one import gives you the tokens:
24
24
  ```
25
25
 
26
26
  So every rule below can be obeyed with a CSS variable, never a hardcoded hex.
27
+
28
+ ## The two prompts (one and one way)
29
+ - **`system.md`** — the design LANGUAGE prompt: paste as a system prompt to make any
30
+ model generate Hanzo-looking output (monochrome, true-black, token-only).
31
+ - **`studio-designer.md`** — the AGENTIC prompt Hanzo Studio's Design System designer
32
+ hands the Hanzo agent stack to GENERATE/EDIT this repo's file tree with its file
33
+ tools, grounded in `guidelines/DESIGN.md`. Uses `system.md`'s language rules.
@@ -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 CHANGED
@@ -12,17 +12,30 @@
12
12
  export * from './tokens.gen.js'
13
13
  import { cssVars, type CssVarName } from './tokens.gen.js'
14
14
 
15
+ /** A token name with the leading `--` omitted: `'background'` for `'--background'`. */
16
+ export type TokenName = CssVarName extends `--${infer N}` ? N : never
17
+
15
18
  /**
16
- * A `var(--name[, fallback])` reference to a token — the ONE way code should
17
- * reach a token so it resolves through the live CSS cascade (honoring the
18
- * viewer's light/dark theme) rather than baking a fixed value.
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)"
19
25
  *
20
- * background: cssVar('--background') // "var(--background)"
21
- * color: cssVar('--foreground', '#fff')
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.
22
34
  */
23
- export function cssVar(name: CssVarName | (string & {}), fallback?: string): string {
24
- const n = name.startsWith('--') ? name : `--${name}`
25
- return fallback ? `var(${n}, ${fallback})` : `var(${n})`
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})`
26
39
  }
27
40
 
28
41
  /** The raw authored value of a token (the literal from the CSS), or `undefined`. */
@@ -35,8 +48,12 @@ export function tokenValue(name: CssVarName): string | undefined {
35
48
  * cannot use a bundler CSS import (e.g. a runtime-mounted island). Prefer the
36
49
  * static `import '@hanzo/design/styles.css'` where a bundler is available.
37
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.
38
55
  */
39
- export function injectDesignCss(href = 'https://esm.sh/@hanzo/design/styles.css'): void {
56
+ export function injectDesignCss(href: string): void {
40
57
  if (typeof document === 'undefined') return
41
58
  if (document.querySelector('link[data-hanzo-design]')) return
42
59
  const l = document.createElement('link')
package/src/tokens.gen.ts CHANGED
@@ -45,7 +45,7 @@ export const colors = {
45
45
  'destructive-foreground': '#f5f5f5',
46
46
  'border': '#1f1f1f',
47
47
  'input': '#1f1f1f',
48
- 'ring': '#333333',
48
+ 'ring': 'var(--neutral-500)',
49
49
  'brand': '#e4e4e7',
50
50
  'brand-foreground': '#09090b',
51
51
  'brand-muted': '#a3a3a3',
@@ -59,7 +59,11 @@ export const colors = {
59
59
  'surface-header': 'rgb(0 0 0 / .7)',
60
60
  'border-hairline': 'var(--neutral-800)',
61
61
  'border-card': 'var(--white-10)',
62
- 'border-strong': 'var(--neutral-700)',
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)',
63
67
  'text-primary': 'var(--pure-white)',
64
68
  'text-secondary': 'var(--white-80)',
65
69
  'text-tertiary': 'var(--white-60)',
@@ -99,6 +103,23 @@ export const typography = {
99
103
  'leading-6xl': '1',
100
104
  'text-7xl': '4rem',
101
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)',
102
123
  'weight-normal': '400',
103
124
  'weight-medium': '500',
104
125
  'weight-semibold': '600',
@@ -181,6 +202,12 @@ export const elevation = {
181
202
  'shadow-floating': '0 25px 50px -12px rgb(0 0 0 / .25)',
182
203
  'shadow-inset-hairline': 'inset 0 0 0 1px var(--white-10)',
183
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)',
184
211
  'glow-hero': 'radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%)',
185
212
  'glow-hero-blur': '120px',
186
213
  'sheen-card': 'radial-gradient(120% 120% at 80% 0%,rgb(255 255 255 / .08) 0%,transparent 55%)',
@@ -214,6 +241,8 @@ export const zIndex = {
214
241
  'z-modal': '600',
215
242
  'z-popover': '700',
216
243
  'z-toast': '800',
244
+ 'z-tooltip': 'var(--z-popover)',
245
+ 'z-notification': 'var(--z-toast)',
217
246
  } as const
218
247
 
219
248
  /** fonts tokens (from tokens/fonts.css). Values are raw CSS. */
@@ -273,7 +302,7 @@ export const cssVars = {
273
302
  '--destructive-foreground': '#f5f5f5',
274
303
  '--border': '#1f1f1f',
275
304
  '--input': '#1f1f1f',
276
- '--ring': '#333333',
305
+ '--ring': 'var(--neutral-500)',
277
306
  '--brand': '#e4e4e7',
278
307
  '--brand-foreground': '#09090b',
279
308
  '--brand-muted': '#a3a3a3',
@@ -287,7 +316,11 @@ export const cssVars = {
287
316
  '--surface-header': 'rgb(0 0 0 / .7)',
288
317
  '--border-hairline': 'var(--neutral-800)',
289
318
  '--border-card': 'var(--white-10)',
290
- '--border-strong': 'var(--neutral-700)',
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)',
291
324
  '--text-primary': 'var(--pure-white)',
292
325
  '--text-secondary': 'var(--white-80)',
293
326
  '--text-tertiary': 'var(--white-60)',
@@ -323,6 +356,23 @@ export const cssVars = {
323
356
  '--leading-6xl': '1',
324
357
  '--text-7xl': '4rem',
325
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)',
326
376
  '--weight-normal': '400',
327
377
  '--weight-medium': '500',
328
378
  '--weight-semibold': '600',
@@ -393,6 +443,12 @@ export const cssVars = {
393
443
  '--shadow-floating': '0 25px 50px -12px rgb(0 0 0 / .25)',
394
444
  '--shadow-inset-hairline': 'inset 0 0 0 1px var(--white-10)',
395
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)',
396
452
  '--glow-hero': 'radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%)',
397
453
  '--glow-hero-blur': '120px',
398
454
  '--sheen-card': 'radial-gradient(120% 120% at 80% 0%,rgb(255 255 255 / .08) 0%,transparent 55%)',
@@ -418,6 +474,8 @@ export const cssVars = {
418
474
  '--z-modal': '600',
419
475
  '--z-popover': '700',
420
476
  '--z-toast': '800',
477
+ '--z-tooltip': 'var(--z-popover)',
478
+ '--z-notification': 'var(--z-toast)',
421
479
  '--font-sans': '"Geist","Geist Sans",ui-sans-serif,system-ui,sans-serif',
422
480
  '--font-display': 'var(--font-sans)',
423
481
  '--font-mono': '"Geist Mono",ui-monospace,SFMono-Regular,monospace',
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}
package/tokens/colors.css CHANGED
@@ -51,7 +51,12 @@
51
51
  --destructive-foreground:#f5f5f5;
52
52
  --border:#1f1f1f;
53
53
  --input:#1f1f1f;
54
- --ring:#333333;
54
+ /* A focus indicator is a NON-TEXT CONTRAST target: WCAG 2.4.11/1.4.11 require
55
+ 3:1 against every surface it can land on. --neutral-500 is the only rung on
56
+ this ladder that clears 3:1 on all of them — #000000, #0a0a0a, #101010,
57
+ #1a1a1a, AND the light theme's #ffffff/#f5f5f5 — so one value serves both
58
+ themes. (Was #333333 = 1.66:1 on --background: not a focus indicator.) */
59
+ --ring:var(--neutral-500);
55
60
  --brand:#e4e4e7;
56
61
  --brand-foreground:#09090b;
57
62
  --brand-muted:#a3a3a3;
@@ -65,9 +70,24 @@
65
70
  --surface-card-quiet:rgb(23 23 23 / .4); /* bg-neutral-900/40 — story cards */
66
71
  --surface-overlay:rgb(10 10 10 / .95); /* dropdown / popover panels */
67
72
  --surface-header:rgb(0 0 0 / .7); /* fixed nav, with backdrop blur */
73
+ /* Boundaries come in two kinds and they are NOT interchangeable.
74
+ DECORATIVE (--border, --border-hairline, --border-card): separates content;
75
+ WCAG imposes no ratio. Keep them quiet.
76
+ PERCEIVABLE (--border-strong): identifies a CONTROL — an input edge, a
77
+ switch, a checkbox — and must clear 3:1 (WCAG 1.4.11) on every surface.
78
+ Reach for --border-strong whenever the boundary IS the affordance. */
68
79
  --border-hairline:var(--neutral-800);
69
80
  --border-card:var(--white-10);
70
- --border-strong:var(--neutral-700);
81
+ --border-strong:var(--neutral-500); /* 3.59:1 worst case — see --ring */
82
+
83
+ /* ——— the numeric surface ladder ——— */
84
+ /* Aliases onto the semantic canvases above, so a brand fork that retunes
85
+ --card/--muted/--secondary retunes the ladder with it and the light theme
86
+ inverts for free. Ascending lift: 0 is the page, 3 is a hovered control. */
87
+ --surface-0:var(--background);
88
+ --surface-1:var(--card);
89
+ --surface-2:var(--muted);
90
+ --surface-3:var(--secondary);
71
91
 
72
92
  /* ——— text ranks ——— */
73
93
  --text-primary:var(--pure-white);
@@ -107,7 +127,10 @@
107
127
  --destructive-foreground:#ffffff;
108
128
  --border:#e5e5e5;
109
129
  --input:#e5e5e5;
110
- --ring:#d4d4d4;
130
+ /* Same rung as dark: #d4d4d4 measured 1.48:1 on white and could not carry a
131
+ focus indicator either. --neutral-500 is 4.74:1 on #ffffff / 4.38:1 on
132
+ #f5f5f5, so ONE value is conformant in both themes. */
133
+ --ring:var(--neutral-500);
111
134
  --black:#0a0a0a;
112
135
  --white:#ffffff;
113
136
  --surface-card:#f5f5f5;
@@ -117,7 +140,10 @@
117
140
  --surface-header:rgb(255 255 255 / .8);
118
141
  --border-hairline:var(--neutral-200);
119
142
  --border-card:rgb(0 0 0 / .1);
120
- --border-strong:var(--neutral-300);
143
+ --border-strong:var(--neutral-500); /* was --neutral-300 = 1.48:1 on white */
144
+ /* The white-opacity ladder does NOT invert, so --white-40 is white-on-white
145
+ here (1.00:1). Anything that needs a visible edge in BOTH themes must use
146
+ --border-strong, never a --white-* rung. */
121
147
  --text-primary:var(--neutral-950);
122
148
  --text-secondary:rgb(10 10 10 / .8);
123
149
  --text-tertiary:rgb(10 10 10 / .6);
@@ -6,6 +6,18 @@
6
6
  --shadow-floating:0 25px 50px -12px rgb(0 0 0 / .25); /* shadow-2xl: composer, dropdowns, mega panel */
7
7
  --shadow-inset-hairline:inset 0 0 0 1px var(--white-10);
8
8
  --ring-focus:0 0 0 2px var(--ring);
9
+
10
+ /* The t-shirt ramp. Named by size rather than by role, because that is how
11
+ every component library already asks for a shadow (and how @hanzo/brand
12
+ spells it). Alphas are heavier than Tailwind's defaults: on a near-black
13
+ ground a 10% black drop is invisible, so each rung is tuned to read on
14
+ --background. --shadow-2xl and --shadow-floating are the same rung. */
15
+ --shadow-sm:0 1px 2px 0 rgb(0 0 0 / .40);
16
+ --shadow:0 1px 3px 0 rgb(0 0 0 / .45), 0 1px 2px -1px rgb(0 0 0 / .45);
17
+ --shadow-md:0 4px 6px -1px rgb(0 0 0 / .50), 0 2px 4px -2px rgb(0 0 0 / .50);
18
+ --shadow-lg:0 10px 15px -3px rgb(0 0 0 / .55), 0 4px 6px -4px rgb(0 0 0 / .55);
19
+ --shadow-xl:0 20px 25px -5px rgb(0 0 0 / .60), 0 8px 10px -6px rgb(0 0 0 / .60);
20
+ --shadow-2xl:var(--shadow-floating);
9
21
  /* Ambient hero glow — a single white radial, blurred 120px, low opacity. */
10
22
  --glow-hero:radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%); /* @kind color */
11
23
  --glow-hero-blur:120px;
package/tokens/fonts.css CHANGED
@@ -1,7 +1,34 @@
1
1
  /* Geist Sans + Geist Mono — the only two faces on Hanzo surfaces.
2
- hanzo.ai loads them via next/font/google (Geist, Geist_Mono); here they come
3
- from the same Google Fonts source, so the rendered face is identical. */
4
- @import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@100..900&display=swap");
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-Geist.txt).
4
+
5
+ Why self-hosted rather than @import from fonts.googleapis.com:
6
+ - A sign-in page must not make a third-party request. hanzoai/id refused to
7
+ import this file for exactly that reason, which split the token layer: id
8
+ took the colours and not the typeface. Self-hosting removes the reason, so
9
+ every surface can import styles.css unchanged.
10
+ - The @import was a render-blocking request to a host we do not control, on
11
+ the critical path of every surface, and it broke offline/air-gapped dev.
12
+ - One variable file per family replaces nine static weights, and it is fewer
13
+ bytes than the CSS-then-woff2 round trip Google served.
14
+
15
+ 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. */
17
+
18
+ @font-face{
19
+ font-family:"Geist";
20
+ src:url("../assets/fonts/Geist-Variable.woff2") format("woff2");
21
+ font-weight:100 900;
22
+ font-style:normal;
23
+ font-display:swap;
24
+ }
25
+ @font-face{
26
+ font-family:"Geist Mono";
27
+ src:url("../assets/fonts/GeistMono-Variable.woff2") format("woff2");
28
+ font-weight:100 900;
29
+ font-style:normal;
30
+ font-display:swap;
31
+ }
5
32
 
6
33
  :root{
7
34
  --font-sans:"Geist","Geist Sans",ui-sans-serif,system-ui,sans-serif;
@@ -16,6 +16,25 @@
16
16
  --text-5xl:2.5rem; --leading-5xl:1.05; /* 40px */
17
17
  --text-6xl:3.25rem; --leading-6xl:1; /* 52px */
18
18
  --text-7xl:4rem; --leading-7xl:1; /* 64px */
19
+ --text-8xl:5.25rem; --leading-8xl:1; /* 84px */
20
+ --text-9xl:7rem; --leading-9xl:1; /* 112px */
21
+
22
+ /* The SAME scale under @hanzo/brand's spelling. @hanzo/gui's shell theme and
23
+ Hanzo Studio address the ramp as --font-size-*; both names are one value, so
24
+ a component written against either resolves here. --text-* is canonical. */
25
+ --font-size-xs:var(--text-xs);
26
+ --font-size-sm:var(--text-sm);
27
+ --font-size-base:var(--text-base);
28
+ --font-size-lg:var(--text-lg);
29
+ --font-size-xl:var(--text-xl);
30
+ --font-size-2xl:var(--text-2xl);
31
+ --font-size-3xl:var(--text-3xl);
32
+ --font-size-4xl:var(--text-4xl);
33
+ --font-size-5xl:var(--text-5xl);
34
+ --font-size-6xl:var(--text-6xl);
35
+ --font-size-7xl:var(--text-7xl);
36
+ --font-size-8xl:var(--text-8xl);
37
+ --font-size-9xl:var(--text-9xl);
19
38
 
20
39
  --weight-normal:400;
21
40
  --weight-medium:500;
package/tokens/z.css CHANGED
@@ -1,14 +1,23 @@
1
1
  /* Stacking order — the one z-index ladder. Layers are named by role, never by a
2
2
  magic number, so a dropdown opened from the fixed header always sits above it
3
- and nothing ever reaches for 9999. Below --z-raised is ordinary document flow. */
3
+ and nothing ever reaches for 9999. Below --z-raised is ordinary document flow.
4
+
5
+ Rungs are 100 apart so a surface can slot a one-off BETWEEN two roles
6
+ (calc(var(--z-modal) + 1)) without inventing a new decade. */
4
7
  :root{
5
8
  --z-base:0;
6
- --z-raised:10; /* hover-lifted cards, sticky table headers */
7
- --z-sticky:200; /* pinned section rails */
8
- --z-header:300; /* the fixed site header */
9
- --z-dropdown:400; /* menus, selects, comboboxes */
10
- --z-overlay:500; /* dialog / sheet scrim */
11
- --z-modal:600; /* dialogs, sheets, command palette */
12
- --z-popover:700; /* popovers, tooltips (also when anchored in modals)*/
13
- --z-toast:800; /* toasts / notifications — always on top */
9
+ --z-raised:10; /* hover-lifted cards, sticky table headers */
10
+ --z-sticky:200; /* pinned section rails */
11
+ --z-header:300; /* the fixed site header */
12
+ --z-dropdown:400; /* menus, selects, comboboxes */
13
+ --z-overlay:500; /* dialog / sheet scrim */
14
+ --z-modal:600; /* dialogs, sheets, command palette */
15
+ --z-popover:700; /* popovers, tooltips (also when anchored in modals) */
16
+ --z-toast:800; /* toasts / notifications — always on top */
17
+
18
+ /* @hanzo/brand spells the top two rungs --z-tooltip and --z-notification.
19
+ Same rungs, so a component written against either vocabulary stacks
20
+ identically. --z-popover / --z-toast are canonical. */
21
+ --z-tooltip:var(--z-popover);
22
+ --z-notification:var(--z-toast);
14
23
  }
File without changes