@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,39 @@
1
+ <!-- @dsCard group="Components" viewport="700x300" name="Forms" subtitle="Input, Label, Textarea, Checkbox, Select, Slider" -->
2
+ <!doctype html><html class="dark"><head><meta charset="utf-8"><title>Form 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 { Input, Label, Textarea, Checkbox, Select, Slider, Icon } = window.HanzoDesignSystem_0a9445;
12
+ function Demo(){
13
+ const [seats,setSeats] = React.useState(24);
14
+ return (<div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:20}}>
15
+ <div className="col">
16
+ <div><Label htmlFor="a" required>Organization name</Label><div style={{height:8}} /><Input id="a" defaultValue="Hanzo AI" /></div>
17
+ <div style={{position:'relative'}}>
18
+ <Icon name="Search" size={16} color="var(--text-helper)" style={{position:'absolute',left:12,top:10}} />
19
+ <Input placeholder="Search agents…" style={{paddingLeft:36}} />
20
+ </div>
21
+ <Input placeholder="you@company.com" invalid defaultValue="not-an-email" />
22
+ <div className="row" style={{gap:20}}>
23
+ <Checkbox label="Auto-recharge" defaultChecked />
24
+ <Checkbox label="Email receipts" />
25
+ </div>
26
+ </div>
27
+ <div className="col">
28
+ <div><Label htmlFor="m">Default model</Label><div style={{height:8}} /><Select id="m" options={[{value:'flash',label:'Enso Flash'},{value:'pro',label:'Enso Pro'},{value:'ultra',label:'Enso Ultra'}]} /></div>
29
+ <Textarea rows={3} placeholder="System prompt…" />
30
+ <div>
31
+ <div style={{display:'flex',justifyContent:'space-between',fontSize:'var(--text-sm)',color:'var(--text-secondary)'}}><span>Seats</span><span style={{color:'#fff'}}>{seats}</span></div>
32
+ <div style={{height:8}} />
33
+ <Slider min={1} max={200} value={seats} onChange={setSeats} />
34
+ </div>
35
+ </div>
36
+ </div>)
37
+ }
38
+ ReactDOM.createRoot(document.getElementById('root')).render(<Demo />);
39
+ </script></body></html>
@@ -0,0 +1,11 @@
1
+ /** Hairline-divided disclosure list. The FAQ pattern on pricing and download pages. */
2
+ export interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ type?: 'single' | 'multiple';
4
+ defaultValue?: string | string[] | null;
5
+ }
6
+ export function Accordion(props: AccordionProps): JSX.Element;
7
+ export interface AccordionItemProps extends React.HTMLAttributes<HTMLDivElement> {
8
+ value: string;
9
+ title: React.ReactNode;
10
+ }
11
+ export function AccordionItem(props: AccordionItemProps): JSX.Element;
@@ -0,0 +1,37 @@
1
+ import React from 'react'
2
+
3
+ const Ctx = React.createContext({ open: null, toggle: () => {} })
4
+
5
+ export function Accordion({ type = 'single', defaultValue = null, style, children, ...rest }) {
6
+ const [open, setOpen] = React.useState(type === 'multiple' ? [].concat(defaultValue || []) : defaultValue)
7
+ const toggle = (v) => {
8
+ if (type === 'multiple') setOpen((cur) => (cur.includes(v) ? cur.filter((x) => x !== v) : [...cur, v]))
9
+ else setOpen((cur) => (cur === v ? null : v))
10
+ }
11
+ const isOpen = (v) => (type === 'multiple' ? open.includes(v) : open === v)
12
+ return (
13
+ <Ctx.Provider value={{ isOpen, toggle }}>
14
+ <div style={{borderTop:'1px solid var(--border-hairline)',...style}} {...rest}>{children}</div>
15
+ </Ctx.Provider>
16
+ )
17
+ }
18
+
19
+ export function AccordionItem({ value, title, style, children, ...rest }) {
20
+ const { isOpen, toggle } = React.useContext(Ctx)
21
+ const on = isOpen(value)
22
+ return (
23
+ <div style={{borderBottom:'1px solid var(--border-hairline)',...style}} {...rest}>
24
+ <button
25
+ onClick={() => toggle(value)} aria-expanded={on}
26
+ style={{display:'flex',width:'100%',alignItems:'center',justifyContent:'space-between',gap:16,padding:'18px 0',background:'transparent',border:'none',cursor:'pointer',textAlign:'left',fontFamily:'var(--font-sans)',fontSize:'var(--text-base)',fontWeight:'var(--weight-medium)',color:'var(--text-primary)'}}
27
+ >
28
+ {title}
29
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--text-helper)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={{flexShrink:0,transform: on ? 'rotate(180deg)' : 'none',transition:'transform var(--duration-fast) var(--ease-out)'}}><polyline points="6 9 12 15 18 9" /></svg>
30
+ </button>
31
+ {on && <div style={{paddingBottom:20,fontSize:'var(--text-sm)',lineHeight:'var(--leading-relaxed)',color:'var(--text-helper)',maxWidth:'62ch'}}>{children}</div>}
32
+ </div>
33
+ )
34
+ }
35
+
36
+ export const AccordionTrigger = ({ children }) => children
37
+ export const AccordionContent = ({ children }) => children
@@ -0,0 +1,10 @@
1
+ FAQ / disclosure list — hairline dividers, chevron rotates on open.
2
+
3
+ ```jsx
4
+ <Accordion defaultValue="q1">
5
+ <AccordionItem value="q1" title="How is usage billed?">Per token, metered hourly.</AccordionItem>
6
+ <AccordionItem value="q2" title="Can I self-host?">Yes — every service is open source.</AccordionItem>
7
+ </Accordion>
8
+ ```
9
+
10
+ Answers cap at ~62ch. Use `type="multiple"` only for long reference lists.
@@ -0,0 +1,10 @@
1
+ /** Segmented tab bar above a panel. Active tab is a neutral-1a chip with a hairline.
2
+ export interface TabsProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ defaultValue?: string;
4
+ value?: string;
5
+ onValueChange?: (value: string) => void;
6
+ }
7
+ export function Tabs(props: TabsProps): JSX.Element;
8
+ export function TabsList(props: React.HTMLAttributes<HTMLDivElement>): JSX.Element;
9
+ export function TabsTrigger(props: React.ButtonHTMLAttributes<HTMLButtonElement> & { value: string }): JSX.Element;
10
+ export function TabsContent(props: React.HTMLAttributes<HTMLDivElement> & { value: string }): JSX.Element;
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+
3
+ const Ctx = React.createContext({ value: null, setValue: () => {} })
4
+
5
+ export function Tabs({ defaultValue, value, onValueChange, style, children, ...rest }) {
6
+ const [inner, setInner] = React.useState(defaultValue)
7
+ const v = value === undefined ? inner : value
8
+ const setValue = (n) => { if (value === undefined) setInner(n); onValueChange && onValueChange(n) }
9
+ return (
10
+ <Ctx.Provider value={{ value: v, setValue }}>
11
+ <div style={{display:'flex',flexDirection:'column',gap:16,...style}} {...rest}>{children}</div>
12
+ </Ctx.Provider>
13
+ )
14
+ }
15
+
16
+ export function TabsList({ style, children, ...rest }) {
17
+ return (
18
+ <div role="tablist" style={{display:'inline-flex',alignItems:'center',gap:4,padding:4,borderRadius:'var(--radius-sm)',background:'var(--muted)',border:'1px solid var(--border)',...style}} {...rest}>{children}</div>
19
+ )
20
+ }
21
+
22
+ export function TabsTrigger({ value, style, children, ...rest }) {
23
+ const { value: active, setValue } = React.useContext(Ctx)
24
+ const on = active === value
25
+ return (
26
+ <button
27
+ role="tab" aria-selected={on} onClick={() => setValue(value)}
28
+ style={{
29
+ display:'inline-flex',alignItems:'center',gap:8,height:30,padding:'0 12px',
30
+ fontFamily:'var(--font-sans)',fontSize:'var(--text-sm)',fontWeight:'var(--weight-medium)',
31
+ color: on ? 'var(--text-primary)' : 'var(--text-helper)',
32
+ background: on ? 'var(--secondary)' : 'transparent',
33
+ border:'1px solid ' + (on ? 'var(--border)' : 'transparent'),
34
+ borderRadius:'calc(var(--radius-sm) - 2px)',cursor:'pointer',whiteSpace:'nowrap',
35
+ transition:'color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out)',
36
+ ...style,
37
+ }}
38
+ {...rest}
39
+ >{children}</button>
40
+ )
41
+ }
42
+
43
+ export function TabsContent({ value, style, children, ...rest }) {
44
+ const { value: active } = React.useContext(Ctx)
45
+ if (active !== value) return null
46
+ return <div role="tabpanel" style={style} {...rest}>{children}</div>
47
+ }
@@ -0,0 +1,11 @@
1
+ Switches between sibling panels — used for language tabs on code samples and for pricing categories.
2
+
3
+ ```jsx
4
+ <Tabs defaultValue="ts">
5
+ <TabsList><TabsTrigger value="ts">TypeScript</TabsTrigger><TabsTrigger value="py">Python</TabsTrigger></TabsList>
6
+ <TabsContent value="ts"><pre>…</pre></TabsContent>
7
+ <TabsContent value="py"><pre>…</pre></TabsContent>
8
+ </Tabs>
9
+ ```
10
+
11
+ The tab bar always stacks above the panel. Keep labels to one or two words.
@@ -0,0 +1,31 @@
1
+ <!-- @dsCard group="Components" viewport="700x300" name="Navigation" subtitle="Tabs and Accordion" -->
2
+ <!doctype html><html class="dark"><head><meta charset="utf-8"><title>Navigation 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="../../_ds_bundle.js"></script>
8
+ <style>body{margin:0;padding:20px;background:#000;font-family:var(--font-sans)}pre{margin:0;padding:16px;background:var(--card);border:1px solid var(--border-hairline);border-radius:var(--radius-lg);font-size:12px;line-height:1.7;color:var(--neutral-300);overflow:auto}</style></head>
9
+ <body><div id="root"></div>
10
+ <script type="text/babel">
11
+ const { Tabs, TabsList, TabsTrigger, TabsContent, Accordion, AccordionItem } = window.HanzoDesignSystem_0a9445;
12
+ const TS = "import Hanzo from '@hanzo/cloud'\n\nconst hanzo = new Hanzo()\nawait hanzo.chat.completions.create({\n model: 'enso-pro',\n messages: [{ role: 'user', content: 'Hi' }],\n})";
13
+ const PY = "from hanzo import Hanzo\n\nhanzo = Hanzo()\nhanzo.chat.completions.create(\n model=\"enso-pro\",\n messages=[{\"role\": \"user\", \"content\": \"Hi\"}],\n)";
14
+ const SH = "curl https://api.hanzo.ai/v1/chat/completions \\\n -H \"Authorization: Bearer $HANZO_API_KEY\" \\\n -d '{\"model\":\"enso-pro\"}'";
15
+ function Demo(){
16
+ return (<div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:24}}>
17
+ <Tabs defaultValue="ts">
18
+ <TabsList><TabsTrigger value="ts">TypeScript</TabsTrigger><TabsTrigger value="py">Python</TabsTrigger><TabsTrigger value="sh">cURL</TabsTrigger></TabsList>
19
+ <TabsContent value="ts"><pre>{TS}</pre></TabsContent>
20
+ <TabsContent value="py"><pre>{PY}</pre></TabsContent>
21
+ <TabsContent value="sh"><pre>{SH}</pre></TabsContent>
22
+ </Tabs>
23
+ <Accordion defaultValue="q1">
24
+ <AccordionItem value="q1" title="How is usage billed?">Per token, metered hourly. You pay only for what you use.</AccordionItem>
25
+ <AccordionItem value="q2" title="Can I self-host?">Yes. Every service in the Hanzo cloud is open source.</AccordionItem>
26
+ <AccordionItem value="q3" title="Which models are available?">Zen open weights, plus Enso Flash, Pro, and Ultra.</AccordionItem>
27
+ </Accordion>
28
+ </div>)
29
+ }
30
+ ReactDOM.createRoot(document.getElementById('root')).render(<Demo />);
31
+ </script></body></html>
@@ -0,0 +1,12 @@
1
+ /** Centred modal on an 80% black scrim with a 4px backdrop blur. Escape and scrim click close it.
2
+ export interface DialogProps {
3
+ open?: boolean;
4
+ onOpenChange?: (open: boolean) => void;
5
+ children?: React.ReactNode;
6
+ }
7
+ export function Dialog(props: DialogProps): JSX.Element | null;
8
+ export function DialogContent(props: React.HTMLAttributes<HTMLDivElement> & { width?: number }): JSX.Element;
9
+ export function DialogHeader(props: React.HTMLAttributes<HTMLDivElement>): JSX.Element;
10
+ export function DialogTitle(props: React.HTMLAttributes<HTMLHeadingElement>): JSX.Element;
11
+ export function DialogDescription(props: React.HTMLAttributes<HTMLParagraphElement>): JSX.Element;
12
+ export function DialogFooter(props: React.HTMLAttributes<HTMLDivElement>): JSX.Element;
@@ -0,0 +1,41 @@
1
+ import React from 'react'
2
+
3
+ export function Dialog({ open, onOpenChange, children }) {
4
+ React.useEffect(() => {
5
+ if (!open) return
6
+ const onKey = (e) => { if (e.key === 'Escape') onOpenChange && onOpenChange(false) }
7
+ document.addEventListener('keydown', onKey)
8
+ return () => document.removeEventListener('keydown', onKey)
9
+ }, [open, onOpenChange])
10
+ if (!open) return null
11
+ return (
12
+ <div
13
+ onClick={() => onOpenChange && onOpenChange(false)}
14
+ style={{position:'fixed',inset:0,zIndex:80,display:'flex',alignItems:'center',justifyContent:'center',padding:24,background:'rgb(0 0 0 / .8)',backdropFilter:'blur(4px)',animation:'hanzo-fade-up var(--duration-fast) var(--ease-out)'}}
15
+ >
16
+ {children}
17
+ </div>
18
+ )
19
+ }
20
+
21
+ export function DialogContent({ width = 480, style, children, ...rest }) {
22
+ return (
23
+ <div
24
+ role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}
25
+ style={{width:'100%',maxWidth:width,maxHeight:'85vh',overflowY:'auto',padding:24,display:'flex',flexDirection:'column',gap:16,background:'var(--popover)',color:'var(--popover-foreground)',border:'1px solid var(--border-hairline)',borderRadius:'var(--radius-xl)',boxShadow:'var(--shadow-floating)',...style}}
26
+ {...rest}
27
+ >{children}</div>
28
+ )
29
+ }
30
+ export function DialogHeader({ style, children, ...rest }) {
31
+ return <div style={{display:'flex',flexDirection:'column',gap:6,...style}} {...rest}>{children}</div>
32
+ }
33
+ export function DialogTitle({ style, children, ...rest }) {
34
+ return <h2 style={{fontSize:'var(--text-lg)',fontWeight:'var(--weight-semibold)',color:'var(--text-primary)',...style}} {...rest}>{children}</h2>
35
+ }
36
+ export function DialogDescription({ style, children, ...rest }) {
37
+ return <p style={{fontSize:'var(--text-sm)',lineHeight:'var(--leading-relaxed)',color:'var(--text-helper)',...style}} {...rest}>{children}</p>
38
+ }
39
+ export function DialogFooter({ style, children, ...rest }) {
40
+ return <div style={{display:'flex',justifyContent:'flex-end',gap:8,...style}} {...rest}>{children}</div>
41
+ }
@@ -0,0 +1,13 @@
1
+ Modal for focused tasks — add credits, agent detail, confirm destructive action.
2
+
3
+ ```jsx
4
+ <Dialog open={open} onOpenChange={setOpen}>
5
+ <DialogContent width={480}>
6
+ <DialogHeader><DialogTitle>Add credits</DialogTitle><DialogDescription>Credits never expire.</DialogDescription></DialogHeader>
7
+ <Input placeholder="$50" />
8
+ <DialogFooter><Button variant="ghost" onClick={()=>setOpen(false)}>Cancel</Button><Button>Add credits</Button></DialogFooter>
9
+ </DialogContent>
10
+ </Dialog>
11
+ ```
12
+
13
+ Escape and scrim click both close. 16px radius (rounded-2xl), hairline border, shadow-2xl.
@@ -0,0 +1,13 @@
1
+ /** Floating menu panel — 16px radius, blurred near-black fill, 8px padding. */
2
+ export interface DropdownMenuProps {
3
+ /** Which edge the panel aligns to. */
4
+ align?: 'left' | 'right';
5
+ /** Open on hover (the nav behaviour) instead of click. */
6
+ hover?: boolean;
7
+ trigger: React.ReactNode;
8
+ children?: React.ReactNode;
9
+ }
10
+ export function DropdownMenu(props: DropdownMenuProps): JSX.Element;
11
+ export function DropdownMenuItem(props: React.AnchorHTMLAttributes<HTMLAnchorElement> & { desc?: string }): JSX.Element;
12
+ export function DropdownMenuLabel(props: React.HTMLAttributes<HTMLSpanElement>): JSX.Element;
13
+ export function DropdownMenuSeparator(props: { style?: React.CSSProperties }): JSX.Element;
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+
3
+ /** Hover-and-click dropdown, matching the nav's Log in / Try Hanzo menus. */
4
+ export function DropdownMenu({ align = 'right', hover = false, trigger, style, children }) {
5
+ const [open, setOpen] = React.useState(false)
6
+ const ref = React.useRef(null)
7
+ React.useEffect(() => {
8
+ const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false) }
9
+ document.addEventListener('mousedown', onDoc)
10
+ return () => document.removeEventListener('mousedown', onDoc)
11
+ }, [])
12
+ return (
13
+ <span
14
+ ref={ref}
15
+ onMouseEnter={hover ? () => setOpen(true) : undefined}
16
+ onMouseLeave={hover ? () => setOpen(false) : undefined}
17
+ style={{position:'relative',display:'inline-flex',...style}}
18
+ >
19
+ <span onClick={() => setOpen((v) => !v)} style={{display:'inline-flex'}}>{trigger}</span>
20
+ {open && (
21
+ <div style={{position:'absolute',top:'100%',[align]:0,paddingTop:12,zIndex:70,minWidth:208}}>
22
+ <div style={{display:'flex',flexDirection:'column',padding:8,background:'var(--surface-overlay)',border:'1px solid var(--border-hairline)',borderRadius:'var(--radius-xl)',boxShadow:'var(--shadow-floating)',backdropFilter:'blur(16px)',animation:'hanzo-fade-down var(--duration-fast) var(--ease-out)'}}>
23
+ {children}
24
+ </div>
25
+ </div>
26
+ )}
27
+ </span>
28
+ )
29
+ }
30
+
31
+ export function DropdownMenuItem({ desc, style, children, ...rest }) {
32
+ const [hover, setHover] = React.useState(false)
33
+ return (
34
+ <a
35
+ onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
36
+ style={{display:'block',padding: desc ? '10px 12px' : '8px 12px',borderRadius:'var(--radius-sm)',background: hover ? 'var(--neutral-900)' : 'transparent',cursor:'pointer',textDecoration:'none',transition:'background-color var(--duration-fast) var(--ease-out)',...style}}
37
+ {...rest}
38
+ >
39
+ <span style={{display:'block',fontSize:'var(--text-sm)',fontWeight:'var(--weight-medium)',color:'var(--neutral-100)'}}>{children}</span>
40
+ {desc && <span style={{display:'block',marginTop:2,fontSize:'var(--text-xs)',color:'var(--neutral-500)'}}>{desc}</span>}
41
+ </a>
42
+ )
43
+ }
44
+ export function DropdownMenuLabel({ style, children, ...rest }) {
45
+ return <span style={{padding:'6px 12px',fontSize:'var(--text-xs)',fontWeight:'var(--weight-medium)',textTransform:'uppercase',letterSpacing:'var(--tracking-wide)',color:'var(--neutral-500)',...style}} {...rest}>{children}</span>
46
+ }
47
+ export function DropdownMenuSeparator({ style }) {
48
+ return <span style={{height:1,margin:'6px 4px',background:'var(--border-hairline)',...style}} />
49
+ }
@@ -0,0 +1,11 @@
1
+ Menu panel for nav actions, account switchers, and row actions.
2
+
3
+ ```jsx
4
+ <DropdownMenu hover align="right" trigger={<Button pill>Try Hanzo <Icon name="ChevronDown" /></Button>}>
5
+ <DropdownMenuItem desc="Chat with frontier models" href="https://hanzo.chat">Hanzo Chat</DropdownMenuItem>
6
+ <DropdownMenuSeparator />
7
+ <DropdownMenuItem href="https://hanzo.app">Hanzo App</DropdownMenuItem>
8
+ </DropdownMenu>
9
+ ```
10
+
11
+ Nav menus open on hover (`hover`); in-app menus open on click. Items support a small `desc` second line.
@@ -0,0 +1,12 @@
1
+ /** Edge-anchored panel — filter drawers on catalog pages, mobile navigation. */
2
+ export interface SheetProps {
3
+ open?: boolean;
4
+ onOpenChange?: (open: boolean) => void;
5
+ side?: 'left' | 'right';
6
+ width?: number;
7
+ children?: React.ReactNode;
8
+ }
9
+ export function Sheet(props: SheetProps): JSX.Element | null;
10
+ export function SheetHeader(props: React.HTMLAttributes<HTMLDivElement>): JSX.Element;
11
+ export function SheetTitle(props: React.HTMLAttributes<HTMLHeadingElement>): JSX.Element;
12
+ export function SheetContent(props: React.HTMLAttributes<HTMLDivElement>): JSX.Element;
@@ -0,0 +1,31 @@
1
+ import React from 'react'
2
+
3
+ export function Sheet({ open, onOpenChange, side = 'right', width = 380, children }) {
4
+ React.useEffect(() => {
5
+ if (!open) return
6
+ const onKey = (e) => { if (e.key === 'Escape') onOpenChange && onOpenChange(false) }
7
+ document.addEventListener('keydown', onKey)
8
+ return () => document.removeEventListener('keydown', onKey)
9
+ }, [open, onOpenChange])
10
+ if (!open) return null
11
+ const pos = side === 'left'
12
+ ? { left: 0, top: 0, bottom: 0, width, borderRight: '1px solid var(--border-hairline)' }
13
+ : { right: 0, top: 0, bottom: 0, width, borderLeft: '1px solid var(--border-hairline)' }
14
+ return (
15
+ <div onClick={() => onOpenChange && onOpenChange(false)} style={{position:'fixed',inset:0,zIndex:80,background:'rgb(0 0 0 / .7)'}}>
16
+ <div
17
+ onClick={(e) => e.stopPropagation()}
18
+ style={{position:'absolute',display:'flex',flexDirection:'column',background:'var(--popover)',boxShadow:'var(--shadow-floating)',animation:'hanzo-fade-up var(--duration-base) var(--ease-out)',...pos}}
19
+ >{children}</div>
20
+ </div>
21
+ )
22
+ }
23
+ export function SheetHeader({ style, children, ...rest }) {
24
+ return <div style={{padding:'20px 24px',borderBottom:'1px solid var(--border-hairline)',display:'flex',flexDirection:'column',gap:4,...style}} {...rest}>{children}</div>
25
+ }
26
+ export function SheetTitle({ style, children, ...rest }) {
27
+ return <h2 style={{fontSize:'var(--text-base)',fontWeight:'var(--weight-semibold)',color:'var(--text-primary)',...style}} {...rest}>{children}</h2>
28
+ }
29
+ export function SheetContent({ style, children, ...rest }) {
30
+ return <div style={{flex:1,overflowY:'auto',padding:24,...style}} {...rest}>{children}</div>
31
+ }
@@ -0,0 +1,10 @@
1
+ Side drawer — used for the OSS-catalog filters and the mobile menu.
2
+
3
+ ```jsx
4
+ <Sheet open={open} onOpenChange={setOpen} side="right" width={380}>
5
+ <SheetHeader><SheetTitle>Filters</SheetTitle></SheetHeader>
6
+ <SheetContent><Checkbox label="Open source" /></SheetContent>
7
+ </Sheet>
8
+ ```
9
+
10
+ Square corners against the viewport edge; no radius. Full-height only.
@@ -0,0 +1,12 @@
1
+ /** Transient confirmation. Mount <Toaster /> once, then call toast() from anywhere. */
2
+ export interface ToasterProps {
3
+ /** Auto-dismiss in ms. */
4
+ duration?: number;
5
+ position?: 'bottom-right' | 'top-right';
6
+ }
7
+ export function Toaster(props: ToasterProps): JSX.Element;
8
+ export interface ToastOptions { description?: string; tone?: 'default' | 'success' | 'error' }
9
+ export const toast: ((message: string, opts?: ToastOptions) => number) & {
10
+ success(message: string, opts?: ToastOptions): number;
11
+ error(message: string, opts?: ToastOptions): number;
12
+ };
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+
3
+ const listeners = new Set()
4
+ let seq = 0
5
+
6
+ /** sonner-style imperative API: toast.success('Agent started'). */
7
+ export const toast = Object.assign(
8
+ (message, opts) => emit({ message, ...opts }),
9
+ {
10
+ success: (message, opts) => emit({ message, tone: 'success', ...opts }),
11
+ error: (message, opts) => emit({ message, tone: 'error', ...opts }),
12
+ }
13
+ )
14
+ function emit(t) {
15
+ const item = { id: ++seq, tone: 'default', ...t }
16
+ listeners.forEach((l) => l(item))
17
+ return item.id
18
+ }
19
+
20
+ export function Toaster({ duration = 3500, position = 'bottom-right' }) {
21
+ const [items, setItems] = React.useState([])
22
+ React.useEffect(() => {
23
+ const add = (item) => {
24
+ setItems((cur) => [...cur, item])
25
+ setTimeout(() => setItems((cur) => cur.filter((i) => i.id !== item.id)), duration)
26
+ }
27
+ listeners.add(add)
28
+ return () => listeners.delete(add)
29
+ }, [duration])
30
+ const anchor = position === 'top-right' ? { top: 24, right: 24 } : { bottom: 24, right: 24 }
31
+ return (
32
+ <div style={{position:'fixed',zIndex:90,display:'flex',flexDirection:'column',gap:8,...anchor}}>
33
+ {items.map((i) => (
34
+ <div key={i.id} style={{
35
+ minWidth:260,maxWidth:380,padding:'12px 14px',display:'flex',alignItems:'flex-start',gap:10,
36
+ background:'var(--surface-overlay)',border:'1px solid ' + (i.tone === 'error' ? 'var(--state-error)' : 'var(--border-hairline)'),
37
+ borderRadius:'var(--radius-lg)',boxShadow:'var(--shadow-floating)',backdropFilter:'blur(16px)',
38
+ fontSize:'var(--text-sm)',color:'var(--neutral-100)',animation:'hanzo-slide-up-fade var(--duration-base) var(--ease-out)',
39
+ }}>
40
+ {i.tone === 'success' && <span style={{width:6,height:6,marginTop:6,borderRadius:'var(--radius-full)',background:'var(--state-online)',flexShrink:0}} />}
41
+ <span>
42
+ {i.message}
43
+ {i.description && <span style={{display:'block',marginTop:2,fontSize:'var(--text-xs)',color:'var(--neutral-500)'}}>{i.description}</span>}
44
+ </span>
45
+ </div>
46
+ ))}
47
+ </div>
48
+ )
49
+ }
@@ -0,0 +1,9 @@
1
+ Transient confirmations after an action succeeds or fails.
2
+
3
+ ```jsx
4
+ <Toaster /> // mount once, near the app root
5
+ toast.success('Agent Atlas started')
6
+ toast.error('Could not reach the gateway', { description: 'Retrying in 5s' })
7
+ ```
8
+
9
+ Bottom-right, auto-dismiss ~3.5s, one line plus an optional quiet second line. Success shows a small green dot — the only hue permitted here.
@@ -0,0 +1,44 @@
1
+ <!-- @dsCard group="Components" viewport="700x320" name="Overlays" subtitle="Dialog, DropdownMenu, Sheet, Toaster" -->
2
+ <!doctype html><html class="dark"><head><meta charset="utf-8"><title>Overlay 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 { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DropdownMenu, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, Sheet, SheetHeader, SheetTitle, SheetContent, Toaster, toast, Button, Input, Icon, Checkbox } = window.HanzoDesignSystem_0a9445;
12
+ function Demo(){
13
+ const [dlg,setDlg] = React.useState(false);
14
+ const [sheet,setSheet] = React.useState(false);
15
+ return (<div className="col">
16
+ <div className="row">
17
+ <Button onClick={()=>setDlg(true)} variant="outline">Open dialog</Button>
18
+ <Button onClick={()=>setSheet(true)} variant="outline">Open sheet</Button>
19
+ <Button onClick={()=>toast.success('Agent Atlas started')} variant="outline">Fire toast</Button>
20
+ <DropdownMenu align="left" trigger={<Button pill>Try Hanzo <Icon name="ChevronDown" /></Button>}>
21
+ <DropdownMenuLabel>Surfaces</DropdownMenuLabel>
22
+ <DropdownMenuItem desc="Chat with frontier models">Hanzo Chat</DropdownMenuItem>
23
+ <DropdownMenuItem desc="One model to command them all">Enso</DropdownMenuItem>
24
+ <DropdownMenuSeparator />
25
+ <DropdownMenuItem desc="Build and ship AI apps">Hanzo App</DropdownMenuItem>
26
+ </DropdownMenu>
27
+ </div>
28
+ <div style={{fontSize:'var(--text-sm)',color:'var(--text-helper)',maxWidth:'60ch'}}>Escape or a scrim click closes any overlay. Panels: 16px radius, hairline border, blurred near-black fill, shadow-2xl.</div>
29
+ <Dialog open={dlg} onOpenChange={setDlg}>
30
+ <DialogContent width={420}>
31
+ <DialogHeader><DialogTitle>Add credits</DialogTitle><DialogDescription>Credits never expire and apply to every Hanzo service.</DialogDescription></DialogHeader>
32
+ <Input defaultValue="$50.00" />
33
+ <DialogFooter><Button variant="ghost" onClick={()=>setDlg(false)}>Cancel</Button><Button onClick={()=>{setDlg(false);toast.success('Credits added')}}>Add credits</Button></DialogFooter>
34
+ </DialogContent>
35
+ </Dialog>
36
+ <Sheet open={sheet} onOpenChange={setSheet} width={320}>
37
+ <SheetHeader><SheetTitle>Filters</SheetTitle></SheetHeader>
38
+ <SheetContent><div className="col"><Checkbox label="Open source" defaultChecked /><Checkbox label="Self-hostable" /><Checkbox label="Enterprise ready" /></div></SheetContent>
39
+ </Sheet>
40
+ <Toaster />
41
+ </div>)
42
+ }
43
+ ReactDOM.createRoot(document.getElementById('root')).render(<Demo />);
44
+ </script></body></html>
@@ -0,0 +1,13 @@
1
+ # content — the words
2
+
3
+ Tokens decide how a Hanzo surface looks. Content decides what it says. Same
4
+ discipline, applied to language: one voice, held everywhere.
5
+
6
+ | File | What |
7
+ |------|------|
8
+ | [`voice.md`](voice.md) | How Hanzo speaks — the rules, with before/after copy. |
9
+ | [`taglines.md`](taglines.md) | Approved headline and tagline lines, by surface. |
10
+
11
+ The atom is the same one that governs the visual system: **understated.** The
12
+ product is the frontier; the copy does not need to shout about it. Numbers over
13
+ adjectives. No exclamation marks. No emoji.
@@ -0,0 +1,46 @@
1
+ # Taglines
2
+
3
+ On-voice lines, grouped by where they run. Pick one, do not stack three. A
4
+ headline earns its space by being specific — prefer the line that names a real
5
+ thing over the line that could belong to any company.
6
+
7
+ ## Master positioning
8
+
9
+ > **Enterprise AI infrastructure and frontier models.**
10
+
11
+ The canonical descriptor (from `brand.json`). Use verbatim in meta descriptions,
12
+ app-store blurbs, and anywhere a single neutral sentence is needed.
13
+
14
+ ## Home hero (pick one)
15
+
16
+ - Frontier models. Your infrastructure.
17
+ - The AI cloud for builders.
18
+ - Run AI. Not plumbing.
19
+ - Inference, agents, and models — one platform.
20
+ - The infrastructure behind frontier AI.
21
+
22
+ ## Product surfaces
23
+
24
+ | Surface | Line |
25
+ |---------|------|
26
+ | Cloud | One platform for inference, agents, and search. |
27
+ | Models | Frontier models, served at cost. |
28
+ | Enso | The agent that reasons before it acts. |
29
+ | Base | Your data. One SQLite file. Yours to keep. |
30
+ | KMS | Secrets that never touch disk in the clear. |
31
+
32
+ ## CTA labels
33
+
34
+ - Get started
35
+ - Start free
36
+ - Read the docs
37
+ - View on GitHub
38
+ - Talk to us
39
+
40
+ ## Rules
41
+
42
+ - ≤ 7 words for a hero headline; ≤ 140 characters for a subline.
43
+ - Sentence case. No trailing exclamation. No emoji.
44
+ - A tagline states a fact or a capability, never a feeling. "The AI cloud for
45
+ builders" is a claim you can test. "Amazing AI experiences" is not — do not
46
+ ship it.