@hanzo/design 0.1.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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/assets/brand/hero-visualization.png +0 -0
  4. package/assets/brand/industry-cloud.png +0 -0
  5. package/assets/brand/industry-engineering.png +0 -0
  6. package/assets/brand/industry-infrastructure.png +0 -0
  7. package/assets/brand/og-image.png +0 -0
  8. package/assets/favicon.svg +8 -0
  9. package/assets/logo-dark.svg +9 -0
  10. package/assets/logo-light.svg +9 -0
  11. package/assets/logo-wordmark.png +0 -0
  12. package/assets/logo.svg +9 -0
  13. package/assets/logos/anthropic.svg +3 -0
  14. package/assets/logos/deepseek.svg +4 -0
  15. package/assets/logos/google.svg +6 -0
  16. package/assets/logos/groq.svg +3 -0
  17. package/assets/logos/huggingface.svg +3 -0
  18. package/assets/logos/mistral.svg +22 -0
  19. package/assets/logos/openai.svg +3 -0
  20. package/assets/logos/openrouter.svg +1 -0
  21. package/assets/logos/partners/aws.svg +12 -0
  22. package/assets/logos/partners/googlecloud.svg +3 -0
  23. package/assets/logos/partners/lux.svg +6 -0
  24. package/assets/logos/partners/nvidia.svg +7 -0
  25. package/assets/logos/partners/techstars.svg +1 -0
  26. package/assets/logos/partners/zoo-labs-foundation.svg +7 -0
  27. package/assets/logos/qwen.svg +1 -0
  28. package/assets/logos/replicate.svg +3 -0
  29. package/assets/logos/together.svg +6 -0
  30. package/assets/logos/xai.svg +3 -0
  31. package/assets/providers/claude.svg +3 -0
  32. package/assets/providers/codex.svg +3 -0
  33. package/assets/providers/copilot.svg +3 -0
  34. package/assets/providers/cursor.svg +3 -0
  35. package/assets/providers/gemini.svg +3 -0
  36. package/assets/providers/grok.svg +4 -0
  37. package/assets/providers/ollama.svg +7 -0
  38. package/components/core/Avatar.d.ts +5 -0
  39. package/components/core/Avatar.jsx +15 -0
  40. package/components/core/Avatar.prompt.md +8 -0
  41. package/components/core/Badge.d.ts +10 -0
  42. package/components/core/Badge.jsx +47 -0
  43. package/components/core/Badge.prompt.md +9 -0
  44. package/components/core/Button.d.ts +9 -0
  45. package/components/core/Button.jsx +48 -0
  46. package/components/core/Button.prompt.md +9 -0
  47. package/components/core/Card.d.ts +15 -0
  48. package/components/core/Card.jsx +46 -0
  49. package/components/core/Card.prompt.md +13 -0
  50. package/components/core/ChromeText.d.ts +10 -0
  51. package/components/core/ChromeText.jsx +29 -0
  52. package/components/core/ChromeText.prompt.md +8 -0
  53. package/components/core/HanzoLogo.d.ts +11 -0
  54. package/components/core/HanzoLogo.jsx +28 -0
  55. package/components/core/HanzoLogo.prompt.md +8 -0
  56. package/components/core/Icon.d.ts +10 -0
  57. package/components/core/Icon.jsx +43 -0
  58. package/components/core/Icon.prompt.md +9 -0
  59. package/components/core/Progress.d.ts +8 -0
  60. package/components/core/Progress.jsx +11 -0
  61. package/components/core/Progress.prompt.md +8 -0
  62. package/components/core/Table.d.ts +11 -0
  63. package/components/core/Table.jsx +30 -0
  64. package/components/core/Table.prompt.md +10 -0
  65. package/components/core/core.card.html +56 -0
  66. package/components/forms/Checkbox.d.ts +10 -0
  67. package/components/forms/Checkbox.jsx +33 -0
  68. package/components/forms/Checkbox.prompt.md +8 -0
  69. package/components/forms/Input.d.ts +3 -0
  70. package/components/forms/Input.jsx +21 -0
  71. package/components/forms/Input.prompt.md +8 -0
  72. package/components/forms/Label.d.ts +3 -0
  73. package/components/forms/Label.jsx +9 -0
  74. package/components/forms/Label.prompt.md +8 -0
  75. package/components/forms/Select.d.ts +9 -0
  76. package/components/forms/Select.jsx +33 -0
  77. package/components/forms/Select.prompt.md +7 -0
  78. package/components/forms/Slider.d.ts +7 -0
  79. package/components/forms/Slider.jsx +22 -0
  80. package/components/forms/Slider.prompt.md +7 -0
  81. package/components/forms/Textarea.d.ts +3 -0
  82. package/components/forms/Textarea.jsx +21 -0
  83. package/components/forms/Textarea.prompt.md +7 -0
  84. package/components/forms/forms.card.html +39 -0
  85. package/components/navigation/Accordion.d.ts +11 -0
  86. package/components/navigation/Accordion.jsx +37 -0
  87. package/components/navigation/Accordion.prompt.md +10 -0
  88. package/components/navigation/Tabs.d.ts +10 -0
  89. package/components/navigation/Tabs.jsx +47 -0
  90. package/components/navigation/Tabs.prompt.md +11 -0
  91. package/components/navigation/navigation.card.html +31 -0
  92. package/components/overlays/Dialog.d.ts +12 -0
  93. package/components/overlays/Dialog.jsx +41 -0
  94. package/components/overlays/Dialog.prompt.md +13 -0
  95. package/components/overlays/DropdownMenu.d.ts +13 -0
  96. package/components/overlays/DropdownMenu.jsx +49 -0
  97. package/components/overlays/DropdownMenu.prompt.md +11 -0
  98. package/components/overlays/Sheet.d.ts +12 -0
  99. package/components/overlays/Sheet.jsx +31 -0
  100. package/components/overlays/Sheet.prompt.md +10 -0
  101. package/components/overlays/Toaster.d.ts +12 -0
  102. package/components/overlays/Toaster.jsx +49 -0
  103. package/components/overlays/Toaster.prompt.md +9 -0
  104. package/components/overlays/overlays.card.html +44 -0
  105. package/content/README.md +13 -0
  106. package/content/taglines.md +46 -0
  107. package/content/voice.md +52 -0
  108. package/docs/README.md +44 -0
  109. package/docs/integrate.md +86 -0
  110. package/guidelines/brand-elevation.card.html +14 -0
  111. package/guidelines/brand-imagery.card.html +16 -0
  112. package/guidelines/brand-mark.card.html +21 -0
  113. package/guidelines/brand-motion.card.html +14 -0
  114. package/guidelines/brand-partner-logos.card.html +23 -0
  115. package/guidelines/brand-radii.card.html +17 -0
  116. package/guidelines/color-borders.card.html +16 -0
  117. package/guidelines/color-neutrals.card.html +24 -0
  118. package/guidelines/color-opacity-ladder.card.html +22 -0
  119. package/guidelines/color-semantic.card.html +17 -0
  120. package/guidelines/color-surfaces.card.html +17 -0
  121. package/guidelines/color-text-ranks.card.html +16 -0
  122. package/guidelines/iconography.card.html +30 -0
  123. package/guidelines/layout-containers.card.html +15 -0
  124. package/guidelines/spacing-golden.card.html +19 -0
  125. package/guidelines/spacing-scale.card.html +23 -0
  126. package/guidelines/type-body.card.html +14 -0
  127. package/guidelines/type-chrome.card.html +14 -0
  128. package/guidelines/type-display.card.html +14 -0
  129. package/guidelines/type-eyebrow.card.html +14 -0
  130. package/guidelines/type-mono.card.html +14 -0
  131. package/guidelines/type-scale.card.html +20 -0
  132. package/package.json +45 -0
  133. package/prompts/README.md +26 -0
  134. package/prompts/pages.md +53 -0
  135. package/prompts/rules.md +58 -0
  136. package/prompts/system.md +102 -0
  137. package/styles.css +11 -0
  138. package/tokens/base.css +12 -0
  139. package/tokens/colors.css +125 -0
  140. package/tokens/elevation.css +19 -0
  141. package/tokens/fonts.css +13 -0
  142. package/tokens/motion.css +24 -0
  143. package/tokens/radius.css +10 -0
  144. package/tokens/spacing.css +45 -0
  145. package/tokens/typography.css +46 -0
  146. package/tokens/z.css +14 -0
  147. package/ui_kits/hanzo_ai_site/SiteChrome.jsx +131 -0
@@ -0,0 +1,46 @@
1
+ import React from 'react'
2
+
3
+ const FILL = {
4
+ default:'var(--surface-card)',
5
+ emphasis:'var(--surface-card-emphasis)',
6
+ quiet:'var(--surface-card-quiet)',
7
+ plain:'transparent',
8
+ }
9
+
10
+ export function Card({ variant = 'default', sheen = false, interactive = false, style, children, ...rest }) {
11
+ const [hover, setHover] = React.useState(false)
12
+ return (
13
+ <div
14
+ onMouseEnter={interactive ? () => setHover(true) : undefined}
15
+ onMouseLeave={interactive ? () => setHover(false) : undefined}
16
+ style={{
17
+ position:'relative',overflow:'hidden',
18
+ borderRadius:'var(--radius-lg)',
19
+ border:'1px solid ' + (hover ? 'var(--border-strong)' : 'var(--border-hairline)'),
20
+ background:FILL[variant] || FILL.default,
21
+ transition:'border-color var(--duration-fast) var(--ease-out)',
22
+ ...style,
23
+ }}
24
+ {...rest}
25
+ >
26
+ {sheen && <div aria-hidden style={{position:'absolute',inset:0,pointerEvents:'none',background:'var(--sheen-card)',opacity:0.7}} />}
27
+ {children}
28
+ </div>
29
+ )
30
+ }
31
+
32
+ export function CardHeader({ style, children, ...rest }) {
33
+ return <div style={{position:'relative',padding:'24px 24px 0',display:'flex',flexDirection:'column',gap:6,...style}} {...rest}>{children}</div>
34
+ }
35
+ export function CardTitle({ style, children, ...rest }) {
36
+ return <h3 style={{fontSize:'var(--text-base)',fontWeight:'var(--weight-semibold)',color:'var(--text-primary)',...style}} {...rest}>{children}</h3>
37
+ }
38
+ export function CardDescription({ style, children, ...rest }) {
39
+ return <p style={{fontSize:'var(--text-sm)',lineHeight:'var(--leading-relaxed)',color:'var(--text-helper)',...style}} {...rest}>{children}</p>
40
+ }
41
+ export function CardContent({ style, children, ...rest }) {
42
+ return <div style={{position:'relative',padding:24,...style}} {...rest}>{children}</div>
43
+ }
44
+ export function CardFooter({ style, children, ...rest }) {
45
+ return <div style={{position:'relative',padding:'0 24px 24px',display:'flex',alignItems:'center',gap:12,...style}} {...rest}>{children}</div>
46
+ }
@@ -0,0 +1,13 @@
1
+ The default grouping surface: 1px hairline border, near-black fill, 12px radius.
2
+
3
+ ```jsx
4
+ <Card interactive sheen>
5
+ <CardContent>
6
+ <Icon name="Shield" size={24} />
7
+ <CardTitle style={{marginTop:12}}>Private by default</CardTitle>
8
+ <CardDescription>Every request is scoped to your org.</CardDescription>
9
+ </CardContent>
10
+ </Card>
11
+ ```
12
+
13
+ Fills: default (grid tiles) · emphasis (featured) · quiet (landing story cards) · plain. Hover only ever brightens the border — cards never lift, scale, or glow.
@@ -0,0 +1,10 @@
1
+ /** Chrome-gradient headline text — the one gradient Hanzo permits. */
2
+ export interface ChromeTextProps extends React.HTMLAttributes<HTMLElement> {
3
+ /** Element to render, e.g. "h1" | "h2". */
4
+ as?: keyof JSX.IntrinsicElements;
5
+ /** chrome = white→white/60 (default). deep = white→neutral-500, for very large display type. */
6
+ tone?: 'chrome' | 'deep';
7
+ /** Optional uppercase eyebrow rendered above the headline. */
8
+ preHeading?: string;
9
+ }
10
+ export function ChromeText(props: ChromeTextProps): JSX.Element;
@@ -0,0 +1,29 @@
1
+ import React from 'react'
2
+
3
+ /** The canonical Hanzo headline treatment: white → white/60 chrome gradient,
4
+ * clipped to the text. Never a saturated or rainbow gradient. */
5
+ export function ChromeText({ as = 'span', tone = 'chrome', preHeading, preHeadingStyle, style, children, ...rest }) {
6
+ const Tag = as
7
+ const gradient = tone === 'deep' ? 'var(--gradient-chrome-2)' : 'var(--gradient-chrome)'
8
+ const node = (
9
+ <Tag
10
+ style={{
11
+ backgroundImage:gradient,
12
+ WebkitBackgroundClip:'text',backgroundClip:'text',
13
+ WebkitTextFillColor:'transparent',color:'transparent',
14
+ display:'inline-block',
15
+ ...style,
16
+ }}
17
+ {...rest}
18
+ >
19
+ {children}
20
+ </Tag>
21
+ )
22
+ if (!preHeading) return node
23
+ return (
24
+ <span style={{display:'flex',flexDirection:'column',gap:12,alignItems:'inherit'}}>
25
+ <span style={{fontSize:'var(--text-xs)',fontWeight:'var(--weight-semibold)',textTransform:'uppercase',letterSpacing:'var(--tracking-widest)',color:'var(--text-helper)',...preHeadingStyle}}>{preHeading}</span>
26
+ {node}
27
+ </span>
28
+ )
29
+ }
@@ -0,0 +1,8 @@
1
+ Wraps hero and section headlines in the chrome gradient (white → white/60).
2
+
3
+ ```jsx
4
+ <ChromeText as="h1" style={{fontSize:'var(--text-6xl)',fontWeight:700}}>Enso, our frontier model</ChromeText>
5
+ <ChromeText as="h2" preHeading="AI cloud" style={{fontSize:'var(--text-4xl)',fontWeight:700}}>Built for agents</ChromeText>
6
+ ```
7
+
8
+ Every product-page h1 uses this. `tone="deep"` (white → neutral-500) is for the very largest display type. Never substitute a hued gradient.
@@ -0,0 +1,11 @@
1
+ /** The Hanzo mark from the press kit.
2
+ export interface HanzoLogoProps {
3
+ /** Rendered square size in px. Nav/footer use 22; hero lockups 80. */
4
+ size?: number;
5
+ /** white on dark surfaces (default), black on light, inherit to take currentColor. */
6
+ variant?: 'white' | 'black' | 'inherit';
7
+ title?: string;
8
+ }
9
+ export function HanzoLogo(props: HanzoLogoProps): JSX.Element;
10
+ export interface HanzoWordmarkProps extends HanzoLogoProps { label?: string }
11
+ export function HanzoWordmark(props: HanzoWordmarkProps): JSX.Element;
@@ -0,0 +1,28 @@
1
+ import React from 'react'
2
+
3
+ /** The Hanzo mark, exactly as shipped in the press kit (assets/logo.svg).
4
+ * Path data is the press-kit source; the light seams stay #DDDDDD. */
5
+ export function HanzoLogo({ size = 22, variant = 'white', title = 'Hanzo', style, ...rest }) {
6
+ const fill = variant === 'white' ? '#ffffff' : variant === 'black' ? 'var(--hanzo-black)' : 'currentColor'
7
+ return (
8
+ <svg viewBox="0 0 67 67" width={size} height={size} xmlns="http://www.w3.org/2000/svg" role="img" aria-label={title} style={{ display: 'block', flexShrink: 0, ...style }} {...rest}>
9
+ <path d="M22.21 67V44.6369H0V67H22.21Z" fill={fill}></path>
10
+ <path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="#DDDDDD"></path>
11
+ <path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill={fill}></path>
12
+ <path d="M22.21 0H0V22.3184H22.21V0Z" fill={fill}></path>
13
+ <path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill={fill}></path>
14
+ <path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="#DDDDDD"></path>
15
+ <path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill={fill}></path>
16
+ </svg>
17
+ )
18
+ }
19
+
20
+ /** Mark + wordmark lockup, as used in the nav and footer. */
21
+ export function HanzoWordmark({ size = 22, label = 'Hanzo AI', variant = 'white', style }) {
22
+ return (
23
+ <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, ...style }}>
24
+ <HanzoLogo size={size} variant={variant} />
25
+ <span style={{ fontSize: 15, fontWeight: 600, letterSpacing: 'var(--tracking-tight)', color: 'var(--text-primary)' }}>{label}</span>
26
+ </span>
27
+ )
28
+ }
@@ -0,0 +1,8 @@
1
+ The Hanzo mark (and mark+wordmark lockup) — use instead of typing "Hanzo" where a logo belongs.
2
+
3
+ ```jsx
4
+ <HanzoLogo size={22} variant="white" />
5
+ <HanzoWordmark label="Hanzo AI" />
6
+ ```
7
+
8
+ Nav and footer use size 22; the wordmark collapses to the bare mark once the page scrolls. Never recolour with hue, never stretch, keep clear space around it.
@@ -0,0 +1,10 @@
1
+ /** Lucide icon glyph, the only icon set Hanzo uses. */
2
+ export interface IconProps extends React.SVGAttributes<SVGSVGElement> {
3
+ /** Lucide icon name, PascalCase or kebab-case: "ArrowRight" | "arrow-right". */
4
+ name: string;
5
+ /** Pixel size. Hanzo sizes: 12 inline, 16 in CTAs, 24 card eyebrow, 40 section accent. */
6
+ size?: number;
7
+ strokeWidth?: number;
8
+ color?: string;
9
+ }
10
+ export function Icon(props: IconProps): JSX.Element;
@@ -0,0 +1,43 @@
1
+ import React from 'react'
2
+
3
+ /** Lucide glyph renderer. Hanzo uses lucide-react 0.462.0 and no other icon set;
4
+ * in a plain browser page the same set arrives as the lucide UMD bundle, which
5
+ * this component reads. Load it once per page:
6
+ * <script src="https://unpkg.com/lucide@0.462.0/dist/umd/lucide.js"></script> */
7
+ const pascal = (n) => n.replace(/(^|[-_ ])(\w)/g, (_, __, c) => c.toUpperCase())
8
+
9
+ function lookup(name) {
10
+ const g = typeof window !== 'undefined' ? window.lucide : null
11
+ if (!g) return null
12
+ const set = g.icons || g
13
+ return set[name] || set[pascal(name)] || null
14
+ }
15
+
16
+ export function Icon({ name, size = 16, strokeWidth = 2, color = 'currentColor', style, ...rest }) {
17
+ const node = lookup(name)
18
+ const kids = []
19
+ if (Array.isArray(node)) {
20
+ node.forEach((child, i) => {
21
+ if (Array.isArray(child)) kids.push(React.createElement(child[0], { key: i, ...child[1] }))
22
+ else if (child && child.tag) kids.push(React.createElement(child.tag, { key: i, ...child.attrs }))
23
+ })
24
+ }
25
+ return (
26
+ <svg
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ width={size}
29
+ height={size}
30
+ viewBox="0 0 24 24"
31
+ fill="none"
32
+ stroke={color}
33
+ strokeWidth={strokeWidth}
34
+ strokeLinecap="round"
35
+ strokeLinejoin="round"
36
+ aria-hidden="true"
37
+ style={{ flexShrink: 0, display: 'block', ...style }}
38
+ {...rest}
39
+ >
40
+ {kids}
41
+ </svg>
42
+ )
43
+ }
@@ -0,0 +1,9 @@
1
+ Renders a lucide glyph — the only icon set on Hanzo surfaces.
2
+
3
+ ```jsx
4
+ <Icon name="ArrowRight" size={16} />
5
+ <Icon name="Shield" size={24} color="var(--text-secondary)" />
6
+ ```
7
+
8
+ Requires the lucide UMD bundle on the page: `<script src="https://unpkg.com/lucide@0.462.0/dist/umd/lucide.js"></script>`.
9
+ Sizes in use: 12 (inline with body text), 16 (button/CTA), 24 (card eyebrow), 40 (section accent). Stroke stays at the default 2. Colour inherits — never tint an icon with hue.
@@ -0,0 +1,8 @@
1
+ /** Thin monochrome meter — white fill on a neutral-800 track. */
2
+ export interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ value?: number;
4
+ max?: number;
5
+ /** Track height in px. 6 in dashboards, 1.5–2 for inline meters. */
6
+ height?: number;
7
+ }
8
+ export function Progress(props: ProgressProps): JSX.Element;
@@ -0,0 +1,11 @@
1
+ import React from 'react'
2
+
3
+ export function Progress({ value = 0, max = 100, height = 6, style, ...rest }) {
4
+ const pct = Math.max(0, Math.min(100, (value / max) * 100))
5
+ return (
6
+ <div role="progressbar" aria-valuenow={value} aria-valuemin={0} aria-valuemax={max}
7
+ style={{position:'relative',width:'100%',height,borderRadius:'var(--radius-full)',background:'var(--neutral-800)',overflow:'hidden',...style}} {...rest}>
8
+ <div style={{height:'100%',width:pct+'%',background:'var(--primary)',borderRadius:'var(--radius-full)',transition:'width var(--duration-base) var(--ease-out)'}} />
9
+ </div>
10
+ )
11
+ }
@@ -0,0 +1,8 @@
1
+ Usage/quota meter. White fill on a neutral-800 track — no colour thresholds.
2
+
3
+ ```jsx
4
+ <Progress value={68} height={6} />
5
+ <Progress value={92} height={2} style={{width:64}} />
6
+ ```
7
+
8
+ Used for agent memory, credit balance, and resource usage. Pair with a numeric label; the bar alone never carries the value.
@@ -0,0 +1,11 @@
1
+ /** Dense data table. Rows separated by neutral-800 hairlines; header sits on pure black. */
2
+ export interface TableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
3
+ /** Adds the neutral-900/60 hover fill — use when the row opens something. */
4
+ interactive?: boolean;
5
+ }
6
+ export function Table(props: React.TableHTMLAttributes<HTMLTableElement>): JSX.Element;
7
+ export function TableHeader(props: React.HTMLAttributes<HTMLTableSectionElement>): JSX.Element;
8
+ export function TableBody(props: React.HTMLAttributes<HTMLTableSectionElement>): JSX.Element;
9
+ export function TableRow(props: TableRowProps): JSX.Element;
10
+ export function TableHead(props: React.ThHTMLAttributes<HTMLTableCellElement> & { align?: 'left' | 'right' | 'center' }): JSX.Element;
11
+ export function TableCell(props: React.TdHTMLAttributes<HTMLTableCellElement> & { align?: 'left' | 'right' | 'center' }): JSX.Element;
@@ -0,0 +1,30 @@
1
+ import React from 'react'
2
+
3
+ export function Table({ style, children, ...rest }) {
4
+ return <table style={{width:'100%',borderCollapse:'collapse',fontSize:'var(--text-sm)',...style}} {...rest}>{children}</table>
5
+ }
6
+ export function TableHeader({ style, children, ...rest }) {
7
+ return <thead style={{background:'var(--black)',...style}} {...rest}>{children}</thead>
8
+ }
9
+ export function TableBody({ style, children, ...rest }) {
10
+ return <tbody style={style} {...rest}>{children}</tbody>
11
+ }
12
+ export function TableRow({ interactive = false, style, children, ...rest }) {
13
+ const [hover, setHover] = React.useState(false)
14
+ return (
15
+ <tr
16
+ onMouseEnter={interactive ? () => setHover(true) : undefined}
17
+ onMouseLeave={interactive ? () => setHover(false) : undefined}
18
+ style={{borderTop:'1px solid var(--border-hairline)',background: hover ? 'rgb(23 23 23 / .6)' : 'transparent',cursor: interactive ? 'pointer' : 'default',transition:'background-color var(--duration-fast) var(--ease-out)',...style}}
19
+ {...rest}
20
+ >
21
+ {children}
22
+ </tr>
23
+ )
24
+ }
25
+ export function TableHead({ align = 'left', style, children, ...rest }) {
26
+ return <th style={{padding:'12px 24px',textAlign:align,fontWeight:'var(--weight-medium)',color:'var(--text-helper)',...style}} {...rest}>{children}</th>
27
+ }
28
+ export function TableCell({ align = 'left', style, children, ...rest }) {
29
+ return <td style={{padding:'16px 24px',textAlign:align,color:'var(--foreground)',...style}} {...rest}>{children}</td>
30
+ }
@@ -0,0 +1,10 @@
1
+ Invoice / agent / usage tables in the console.
2
+
3
+ ```jsx
4
+ <Table>
5
+ <TableHeader><TableRow><TableHead>Invoice</TableHead><TableHead align="right">Amount</TableHead></TableRow></TableHeader>
6
+ <TableBody><TableRow interactive><TableCell>INV-20230501</TableCell><TableCell align="right">$25.00</TableCell></TableRow></TableBody>
7
+ </Table>
8
+ ```
9
+
10
+ Wrap in a `Card` for the rounded hairline frame. Status cells use `Badge` with the opacity ladder, not coloured chips.
@@ -0,0 +1,56 @@
1
+ <!-- @dsCard group="Components" viewport="700x340" name="Core" subtitle="Button, Badge, Card, Progress, Avatar, ChromeText, Icon" -->
2
+ <!doctype html><html class="dark"><head><meta charset="utf-8"><title>Core components</title>
3
+ <link rel="stylesheet" href="../../styles.css">
4
+ <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
5
+ <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
6
+ <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
7
+ <script src="https://unpkg.com/lucide@0.462.0/dist/umd/lucide.js"></script>
8
+ <script src="../../_ds_bundle.js"></script>
9
+ <style>body{margin:0;padding:20px;background:#000;font-family:var(--font-sans)}.row{display:flex;flex-wrap:wrap;align-items:center;gap:10px}.col{display:flex;flex-direction:column;gap:14px}.lbl{font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--neutral-600)}</style></head><body><div id="root"></div>
10
+ <script type="text/babel">
11
+ const { Button, Badge, StatusBadge, Card, CardContent, CardTitle, CardDescription, ChromeText, Progress, Avatar, AvatarFallback, Icon, HanzoLogo } = window.HanzoDesignSystem_0a9445;
12
+ function Demo(){
13
+ return (<div className="col">
14
+ <div className="row">
15
+ <Button pill>Try Hanzo <Icon name="ChevronDown" /></Button>
16
+ <Button variant="secondary">Secondary</Button>
17
+ <Button variant="outline"><Icon name="Filter" /> Filter</Button>
18
+ <Button variant="ghost">Ghost</Button>
19
+ <Button variant="destructive">Delete</Button>
20
+ <Button variant="link">Read the docs</Button>
21
+ <Button variant="ghost" size="icon"><Icon name="Settings" /></Button>
22
+ <Button disabled>Disabled</Button>
23
+ <Button size="sm" variant="outline">Small</Button>
24
+ <Button size="lg" pill>Get started <Icon name="ArrowRight" /></Button>
25
+ </div>
26
+ <div className="row">
27
+ <Badge>Default</Badge><Badge variant="tier1">Edge</Badge><Badge variant="tier2">Pro</Badge><Badge variant="tier3">Ultra</Badge>
28
+ <Badge variant="outline">Open source</Badge><Badge variant="error">Failed</Badge>
29
+ <StatusBadge status="online" label="All systems normal" />
30
+ <Avatar size={28}><AvatarFallback>ZK</AvatarFallback></Avatar>
31
+ <Avatar size={28} />
32
+ <HanzoLogo size={22} />
33
+ </div>
34
+ <div className="row" style={{alignItems:'stretch'}}>
35
+ <Card interactive sheen style={{flex:1,minWidth:200}}>
36
+ <CardContent>
37
+ <Icon name="Shield" size={24} color="var(--text-secondary)" />
38
+ <CardTitle style={{marginTop:12}}>Private by default</CardTitle>
39
+ <CardDescription>Every request scoped to your org.</CardDescription>
40
+ </CardContent>
41
+ </Card>
42
+ <Card variant="emphasis" style={{flex:1,minWidth:200}}>
43
+ <CardContent>
44
+ <div className="lbl">Credits</div>
45
+ <div style={{fontSize:'var(--text-2xl)',fontWeight:600,color:'#fff',margin:'6px 0 10px'}}>$412.80</div>
46
+ <Progress value={68} />
47
+ </CardContent>
48
+ </Card>
49
+ <Card variant="plain" style={{flex:1,minWidth:200,display:'flex',alignItems:'center',justifyContent:'center',padding:16}}>
50
+ <ChromeText as="div" style={{fontSize:'var(--text-3xl)',fontWeight:700,letterSpacing:'-.025em'}}>Chrome text</ChromeText>
51
+ </Card>
52
+ </div>
53
+ </div>)
54
+ }
55
+ ReactDOM.createRoot(document.getElementById('root')).render(<Demo />);
56
+ </script></body></html>
@@ -0,0 +1,10 @@
1
+ /** Square 16px checkbox; checked state is a white fill with a black tick. */
2
+ export interface CheckboxProps {
3
+ checked?: boolean;
4
+ defaultChecked?: boolean;
5
+ onChange?: (checked: boolean) => void;
6
+ disabled?: boolean;
7
+ /** Optional inline label rendered to the right. */
8
+ label?: React.ReactNode;
9
+ }
10
+ export function Checkbox(props: CheckboxProps): JSX.Element;
@@ -0,0 +1,33 @@
1
+ import React from 'react'
2
+
3
+ export function Checkbox({ checked, defaultChecked, onChange, disabled, label, style, ...rest }) {
4
+ const [inner, setInner] = React.useState(!!defaultChecked)
5
+ const on = checked === undefined ? inner : checked
6
+ const toggle = () => {
7
+ if (disabled) return
8
+ if (checked === undefined) setInner(!on)
9
+ onChange && onChange(!on)
10
+ }
11
+ const box = (
12
+ <span
13
+ role="checkbox" aria-checked={on} tabIndex={disabled ? -1 : 0}
14
+ onClick={toggle}
15
+ onKeyDown={(e)=>{if(e.key===' '||e.key==='Enter'){e.preventDefault();toggle()}}}
16
+ style={{
17
+ display:'inline-flex',alignItems:'center',justifyContent:'center',width:16,height:16,flexShrink:0,
18
+ borderRadius:4,border:'1px solid ' + (on ? 'var(--primary)' : 'var(--border-strong)'),
19
+ background: on ? 'var(--primary)' : 'transparent',
20
+ cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.5 : 1,
21
+ transition:'background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out)',
22
+ ...style,
23
+ }}
24
+ {...rest}
25
+ >
26
+ {on && (
27
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="var(--primary-foreground)" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12" /></svg>
28
+ )}
29
+ </span>
30
+ )
31
+ if (!label) return box
32
+ return <span style={{display:'inline-flex',alignItems:'center',gap:8,fontSize:'var(--text-sm)',color:'var(--text-secondary)'}}>{box}{label}</span>
33
+ }
@@ -0,0 +1,8 @@
1
+ Checkbox for opt-ins and multi-select filters.
2
+
3
+ ```jsx
4
+ <Checkbox label="Auto-recharge credits" defaultChecked />
5
+ <Checkbox checked={agree} onChange={setAgree} label="I agree to the terms" />
6
+ ```
7
+
8
+ Checked = white fill + black tick. There is no indeterminate state in the Hanzo set.
@@ -0,0 +1,3 @@
1
+ /** Single-line text field. Black fill, neutral-800 hairline, ring-coloured focus border. */
2
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { invalid?: boolean }
3
+ export function Input(props: InputProps): JSX.Element;
@@ -0,0 +1,21 @@
1
+ import React from 'react'
2
+
3
+ export function Input({ invalid = false, style, ...rest }) {
4
+ const [focus, setFocus] = React.useState(false)
5
+ return (
6
+ <input
7
+ onFocus={(e)=>{setFocus(true);rest.onFocus&&rest.onFocus(e)}}
8
+ onBlur={(e)=>{setFocus(false);rest.onBlur&&rest.onBlur(e)}}
9
+ style={{
10
+ width:'100%',height:36,padding:'0 12px',
11
+ fontFamily:'var(--font-sans)',fontSize:'var(--text-sm)',color:'var(--foreground)',
12
+ background:'var(--black)',
13
+ border:'1px solid ' + (invalid ? 'var(--state-error)' : focus ? 'var(--ring)' : 'var(--border)'),
14
+ borderRadius:'var(--radius-sm)',outline:'none',
15
+ transition:'border-color var(--duration-fast) var(--ease-out)',
16
+ ...style,
17
+ }}
18
+ {...rest}
19
+ />
20
+ )
21
+ }
@@ -0,0 +1,8 @@
1
+ Text field for search, forms, and settings.
2
+
3
+ ```jsx
4
+ <Input placeholder="Search agents…" />
5
+ <Input type="email" invalid aria-describedby="err" />
6
+ ```
7
+
8
+ For search fields, place a 16px lucide `Search` icon absolutely at 12px from the left and pad the input to 36px. Focus shows a `--ring` border, never a removed outline.
@@ -0,0 +1,3 @@
1
+ /** Field label — 14px medium at 80% white. */
2
+ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> { required?: boolean }
3
+ export function Label(props: LabelProps): JSX.Element;
@@ -0,0 +1,9 @@
1
+ import React from 'react'
2
+
3
+ export function Label({ required = false, style, children, ...rest }) {
4
+ return (
5
+ <label style={{display:'inline-block',fontSize:'var(--text-sm)',fontWeight:'var(--weight-medium)',color:'var(--text-secondary)',...style}} {...rest}>
6
+ {children}{required && <span aria-hidden style={{color:'var(--text-helper)'}}> *</span>}
7
+ </label>
8
+ )
9
+ }
@@ -0,0 +1,8 @@
1
+ Field label. Always `htmlFor` the control it names.
2
+
3
+ ```jsx
4
+ <Label htmlFor="org" required>Organization name</Label>
5
+ <Input id="org" />
6
+ ```
7
+
8
+ Stack label → control with 8px gap; helper text goes below the control in `--text-xs` / `--text-helper`.
@@ -0,0 +1,9 @@
1
+ /** Dropdown select. Same chrome as Input plus a chevron. */
2
+ export interface SelectOption { value: string; label: string }
3
+ export interface SelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'onChange'> {
4
+ options?: (SelectOption | string)[];
5
+ value?: string;
6
+ onChange?: (value: string) => void;
7
+ placeholder?: string;
8
+ }
9
+ export function Select(props: SelectProps): JSX.Element;
@@ -0,0 +1,33 @@
1
+ import React from 'react'
2
+
3
+ /** Native-select build of the @hanzo/ui Select — same chrome, no portal. */
4
+ export function Select({ options = [], value, onChange, placeholder, style, children, ...rest }) {
5
+ const [focus, setFocus] = React.useState(false)
6
+ return (
7
+ <span style={{position:'relative',display:'inline-flex',alignItems:'center',width:'100%'}}>
8
+ <select
9
+ value={value}
10
+ onChange={(e)=>onChange && onChange(e.target.value)}
11
+ onFocus={()=>setFocus(true)} onBlur={()=>setFocus(false)}
12
+ style={{
13
+ appearance:'none',width:'100%',height:36,padding:'0 32px 0 12px',
14
+ fontFamily:'var(--font-sans)',fontSize:'var(--text-sm)',color:'var(--foreground)',
15
+ background:'var(--black)',border:'1px solid ' + (focus ? 'var(--ring)' : 'var(--border)'),
16
+ borderRadius:'var(--radius-sm)',outline:'none',cursor:'pointer',
17
+ transition:'border-color var(--duration-fast) var(--ease-out)',
18
+ ...style,
19
+ }}
20
+ {...rest}
21
+ >
22
+ {placeholder && <option value="">{placeholder}</option>}
23
+ {options.map((o) => {
24
+ const v = typeof o === 'string' ? o : o.value
25
+ const l = typeof o === 'string' ? o : o.label
26
+ return <option key={v} value={v}>{l}</option>
27
+ })}
28
+ {children}
29
+ </select>
30
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--text-helper)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={{position:'absolute',right:10,pointerEvents:'none'}}><polyline points="6 9 12 15 18 9" /></svg>
31
+ </span>
32
+ )
33
+ }
@@ -0,0 +1,7 @@
1
+ Single-choice dropdown for settings and filters.
2
+
3
+ ```jsx
4
+ <Select value={org} onChange={setOrg} options={[{value:'hanzo',label:'Hanzo AI'},{value:'zoo',label:'Zoo Labs'}]} />
5
+ ```
6
+
7
+ For 2–3 short options prefer `Tabs` or a segmented row of `Button variant="outline"`; reach for Select at 4+.
@@ -0,0 +1,7 @@
1
+ /** Range control — white filled track on neutral-800, used on the pricing/team-size calculators. */
2
+ export interface SliderProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'> {
3
+ min?: number; max?: number; step?: number;
4
+ value?: number; defaultValue?: number;
5
+ onChange?: (value: number) => void;
6
+ }
7
+ export function Slider(props: SliderProps): JSX.Element;
@@ -0,0 +1,22 @@
1
+ import React from 'react'
2
+
3
+ export function Slider({ min = 0, max = 100, step = 1, value, defaultValue = 0, onChange, style, ...rest }) {
4
+ const [inner, setInner] = React.useState(defaultValue)
5
+ const v = value === undefined ? inner : value
6
+ const pct = ((v - min) / (max - min)) * 100
7
+ return (
8
+ <input
9
+ type="range" min={min} max={max} step={step} value={v}
10
+ onChange={(e) => { const n = Number(e.target.value); if (value === undefined) setInner(n); onChange && onChange(n) }}
11
+ style={{
12
+ appearance:'none',WebkitAppearance:'none',width:'100%',height:16,background:'transparent',cursor:'pointer',
13
+ backgroundImage:'linear-gradient(var(--primary),var(--primary)),linear-gradient(var(--neutral-800),var(--neutral-800))',
14
+ backgroundSize: pct + '% 4px, 100% 4px',
15
+ backgroundPosition:'left center, left center',
16
+ backgroundRepeat:'no-repeat',
17
+ ...style,
18
+ }}
19
+ {...rest}
20
+ />
21
+ )
22
+ }
@@ -0,0 +1,7 @@
1
+ Numeric range — seats, credits, spend caps.
2
+
3
+ ```jsx
4
+ <Slider min={1} max={200} value={seats} onChange={setSeats} />
5
+ ```
6
+
7
+ Always render the current value as text beside or above the slider; the track alone never communicates the number.
@@ -0,0 +1,3 @@
1
+ /** Multi-line field. Same chrome as Input, vertical resize only. */
2
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
3
+ export function Textarea(props: TextareaProps): JSX.Element;
@@ -0,0 +1,21 @@
1
+ import React from 'react'
2
+
3
+ export function Textarea({ rows = 4, style, ...rest }) {
4
+ const [focus, setFocus] = React.useState(false)
5
+ return (
6
+ <textarea
7
+ rows={rows}
8
+ onFocus={(e)=>{setFocus(true);rest.onFocus&&rest.onFocus(e)}}
9
+ onBlur={(e)=>{setFocus(false);rest.onBlur&&rest.onBlur(e)}}
10
+ style={{
11
+ width:'100%',padding:'8px 12px',resize:'vertical',
12
+ fontFamily:'var(--font-sans)',fontSize:'var(--text-sm)',lineHeight:'var(--leading-relaxed)',color:'var(--foreground)',
13
+ background:'var(--black)',border:'1px solid ' + (focus ? 'var(--ring)' : 'var(--border)'),
14
+ borderRadius:'var(--radius-sm)',outline:'none',
15
+ transition:'border-color var(--duration-fast) var(--ease-out)',
16
+ ...style,
17
+ }}
18
+ {...rest}
19
+ />
20
+ )
21
+ }
@@ -0,0 +1,7 @@
1
+ Multi-line input for descriptions, prompts, and notes.
2
+
3
+ ```jsx
4
+ <Textarea rows={5} placeholder="System prompt…" />
5
+ ```
6
+
7
+ The chat composer is NOT this component — it is a borderless textarea inside a 28px-radius pill (see the site UI kit).