feat: Initial Hanzo brand package

- Complete brand configuration and assets structure
- React logo components with variants
- Color system with light/dark themes
- Typography system with custom fonts
- Design tokens for consistent styling
- Utility functions for brand usage
- Comprehensive documentation
This commit is contained in:
2025-09-15 18:52:15 -05:00
commit 1a5a23d4fc
12 changed files with 1486 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
# Dependencies
node_modules/
.pnp
.pnp.js
# Build outputs
dist/
build/
out/
.next/
# Testing
coverage/
*.lcov
.nyc_output
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Environment
.env
.env.local
.env.production.local
.env.development.local
.env.test.local
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Cache
.npm
.eslintcache
.turbo
# Misc
*.log
tmp/
temp/
+288
View File
@@ -0,0 +1,288 @@
# Hanzo Brand
Official brand assets, design system, and guidelines for Hanzo AI.
<div align="center">
<img src="assets/logo/hanzo-logo.svg" alt="Hanzo Logo" width="120" />
**Building the future of AI infrastructure**
[![npm version](https://img.shields.io/npm/v/@hanzo/brand)](https://www.npmjs.com/package/@hanzo/brand)
[![License](https://img.shields.io/github/license/hanzoai/brand)](LICENSE)
[![Downloads](https://img.shields.io/npm/dm/@hanzo/brand)](https://www.npmjs.com/package/@hanzo/brand)
</div>
## 🎨 Quick Start
### Installation
```bash
# npm
npm install @hanzo/brand
# pnpm
pnpm add @hanzo/brand
# yarn
yarn add @hanzo/brand
```
### Usage
```typescript
import {
colors,
logos,
typography,
HanzoLogo,
applyTheme
} from '@hanzo/brand'
// Use the logo component
<HanzoLogo size="medium" variant="dark" />
// Apply brand colors
const primaryColor = colors.primary.DEFAULT
const backgroundColor = colors.background.DEFAULT
// Apply complete theme
applyTheme('dark')
```
## 📁 Repository Structure
```
@hanzo/brand/
├── assets/ # Raw brand assets
│ ├── logo/ # Logo variations (SVG, PNG)
│ ├── icons/ # Brand icons
│ ├── images/ # Brand images and illustrations
│ └── graphics/ # Other graphics
├── fonts/ # Brand typography
│ ├── inter/ # Primary font
│ └── mono/ # Monospace font
├── styles/ # CSS and design tokens
│ ├── colors.css # Color variables
│ ├── typography.css # Typography styles
│ └── themes/ # Theme variations
├── src/ # Source code
│ ├── index.ts # Main export
│ ├── colors.ts # Color definitions
│ ├── typography.ts # Typography config
│ ├── logos.tsx # Logo components
│ └── utils.ts # Helper functions
└── guidelines/ # Brand guidelines
├── visual.md # Visual guidelines
├── voice.md # Voice and tone
└── usage.md # Usage examples
```
## 🎨 Brand Colors
### Primary Palette
| Color | Hex | Usage |
|-------|-----|-------|
| **Primary** | `#0A0A0B` | Main brand color, CTAs |
| **Secondary** | `#F5F5F5` | Supporting elements |
| **Accent** | `#3B82F6` | Interactive elements |
| **Success** | `#10B981` | Success states |
| **Warning** | `#F59E0B` | Warning states |
| **Error** | `#EF4444` | Error states |
### Using Colors
```javascript
import { colors } from '@hanzo/brand'
// In CSS-in-JS
const styles = {
color: colors.primary.DEFAULT,
backgroundColor: colors.background.light
}
// CSS Variables
// Automatically injected when using applyTheme()
// var(--hanzo-primary)
// var(--hanzo-secondary)
// var(--hanzo-background)
```
## 🔤 Typography
### Font Stack
- **Primary**: Inter, system-ui, sans-serif
- **Monospace**: 'JetBrains Mono', 'Fira Code', monospace
- **Display**: Inter Display, Inter, sans-serif
### Usage
```javascript
import { typography } from '@hanzo/brand'
// Get font family
const { fontFamily, fontSize, fontWeight } = typography
// Apply typography styles
const headingStyle = typography.heading.h1
const bodyStyle = typography.body.large
```
## 🖼️ Logo Usage
### Logo Variants
The Hanzo logo is available in multiple variants:
- **Full Logo**: Complete logo with text
- **Mark Only**: Icon mark without text
- **Horizontal**: Horizontal layout
- **Stacked**: Vertical stacked layout
### React Component
```tsx
import { HanzoLogo } from '@hanzo/brand'
// Basic usage
<HanzoLogo />
// With options
<HanzoLogo
variant="full" // full | mark | horizontal | stacked
size="medium" // small | medium | large | xl
theme="dark" // light | dark | auto
className="my-logo"
/>
```
### Static Assets
```javascript
import { logos } from '@hanzo/brand'
// Get logo URLs
const logoUrl = logos.full.dark.svg
const markUrl = logos.mark.light.png
```
## 🎯 Design Tokens
```javascript
import { tokens } from '@hanzo/brand'
// Spacing
tokens.spacing.xs // 4px
tokens.spacing.sm // 8px
tokens.spacing.md // 16px
tokens.spacing.lg // 24px
tokens.spacing.xl // 32px
// Border radius
tokens.radius.sm // 4px
tokens.radius.md // 8px
tokens.radius.lg // 16px
tokens.radius.full // 9999px
// Shadows
tokens.shadow.sm
tokens.shadow.md
tokens.shadow.lg
```
## 🌙 Theming
### Apply Theme
```javascript
import { applyTheme, themes } from '@hanzo/brand'
// Apply default theme
applyTheme()
// Apply specific theme
applyTheme('dark')
applyTheme('light')
// Get theme configuration
const darkTheme = themes.dark
const lightTheme = themes.light
```
### Custom Theme
```javascript
import { createTheme } from '@hanzo/brand'
const customTheme = createTheme({
colors: {
primary: '#custom-color',
// ... other colors
},
typography: {
// ... typography config
}
})
applyTheme(customTheme)
```
## 📋 Brand Guidelines
### Voice and Tone
- **Professional**: Clear, concise communication
- **Innovative**: Forward-thinking and cutting-edge
- **Accessible**: Inclusive and easy to understand
- **Trustworthy**: Reliable and transparent
### Visual Guidelines
1. **Logo Clear Space**: Maintain minimum clear space equal to the height of the 'H' in Hanzo
2. **Minimum Size**: Logo should not be displayed smaller than 24px in height
3. **Color Usage**: Use approved brand colors only
4. **Typography**: Use Inter for all text, weights 400-700
### Do's and Don'ts
✅ **DO**
- Use official brand assets
- Maintain proper contrast ratios
- Follow spacing guidelines
- Keep consistent styling
❌ **DON'T**
- Modify logo proportions
- Use off-brand colors
- Create unofficial variations
- Compress or stretch logos
## 🔗 Related Packages
- [@hanzo/ui](https://github.com/hanzoai/ui) - UI component library
- [@hanzo/cli](https://github.com/hanzoai/cli) - Command-line tools
- [@hanzo/sdk](https://github.com/hanzoai/sdk) - JavaScript SDK
## 📚 Resources
- [Brand Portal](https://brand.hanzo.ai)
- [Design System](https://design.hanzo.ai)
- [Component Library](https://ui.hanzo.ai)
- [Documentation](https://docs.hanzo.ai)
## 🤝 Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
## 📄 License
MIT © Hanzo AI
---
<div align="center">
Made with ❤️ by the Hanzo team
[Website](https://hanzo.ai) • [GitHub](https://github.com/hanzoai) • [Twitter](https://twitter.com/hanzoai)
</div>
+47
View File
@@ -0,0 +1,47 @@
{
"name": "@hanzo/brand",
"version": "1.0.0",
"description": "Official brand assets and design system for Hanzo AI",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist",
"assets",
"fonts",
"styles"
],
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
"lint": "eslint src",
"type-check": "tsc --noEmit"
},
"keywords": [
"hanzo",
"brand",
"design-system",
"ui",
"assets",
"logo",
"colors",
"typography"
],
"author": "Hanzo AI",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/hanzoai/brand"
},
"homepage": "https://github.com/hanzoai/brand",
"devDependencies": {
"@types/node": "^20.0.0",
"eslint": "^8.0.0",
"tsup": "^8.0.0",
"typescript": "^5.0.0"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
+155
View File
@@ -0,0 +1,155 @@
/**
* Hanzo brand colors
*/
export const colors = {
// Primary colors
primary: {
DEFAULT: '#0A0A0B',
50: '#F5F5F5',
100: '#E5E5E5',
200: '#C4C4C4',
300: '#A3A3A3',
400: '#737373',
500: '#525252',
600: '#404040',
700: '#262626',
800: '#171717',
900: '#0A0A0B',
950: '#050505',
},
// Secondary colors
secondary: {
DEFAULT: '#F5F5F5',
dark: '#E5E5E5',
darker: '#C4C4C4',
},
// Accent colors
accent: {
DEFAULT: '#3B82F6',
light: '#60A5FA',
dark: '#2563EB',
},
// Semantic colors
success: {
DEFAULT: '#10B981',
light: '#34D399',
dark: '#059669',
bg: '#ECFDF5',
border: '#A7F3D0',
},
warning: {
DEFAULT: '#F59E0B',
light: '#FCD34D',
dark: '#D97706',
bg: '#FFFBEB',
border: '#FDE68A',
},
error: {
DEFAULT: '#EF4444',
light: '#F87171',
dark: '#DC2626',
bg: '#FEF2F2',
border: '#FECACA',
},
info: {
DEFAULT: '#3B82F6',
light: '#60A5FA',
dark: '#2563EB',
bg: '#EFF6FF',
border: '#BFDBFE',
},
// Neutral colors
neutral: {
0: '#FFFFFF',
50: '#FAFAFA',
100: '#F5F5F5',
200: '#E5E5E5',
300: '#D4D4D4',
400: '#A3A3A3',
500: '#737373',
600: '#525252',
700: '#404040',
800: '#262626',
900: '#171717',
950: '#0A0A0A',
1000: '#000000',
},
// Background colors
background: {
DEFAULT: '#FFFFFF',
secondary: '#FAFAFA',
tertiary: '#F5F5F5',
dark: '#0A0A0B',
darkSecondary: '#171717',
darkTertiary: '#262626',
},
// Text colors
text: {
primary: '#0A0A0B',
secondary: '#525252',
tertiary: '#737373',
disabled: '#A3A3A3',
inverse: '#FFFFFF',
link: '#3B82F6',
linkHover: '#2563EB',
},
// Border colors
border: {
DEFAULT: '#E5E5E5',
light: '#F5F5F5',
dark: '#D4D4D4',
focus: '#3B82F6',
},
// Special colors
code: {
bg: '#F5F5F5',
text: '#0A0A0B',
border: '#E5E5E5',
darkBg: '#1E1E1E',
darkText: '#D4D4D4',
darkBorder: '#404040',
},
// Gradients
gradients: {
primary: 'linear-gradient(135deg, #0A0A0B 0%, #262626 100%)',
accent: 'linear-gradient(135deg, #3B82F6 0%, #2563EB 100%)',
sunset: 'linear-gradient(135deg, #F59E0B 0%, #EF4444 100%)',
ocean: 'linear-gradient(135deg, #3B82F6 0%, #10B981 100%)',
},
} as const
// CSS variable generator
export function getCSSVariables(prefix = 'hanzo') {
const cssVars: Record<string, string> = {}
// Flatten the color object
function flatten(obj: any, parentKey = '') {
Object.entries(obj).forEach(([key, value]) => {
const newKey = parentKey ? `${parentKey}-${key}` : key
if (typeof value === 'object' && !value.includes?.('gradient')) {
flatten(value, newKey)
} else {
const varName = `--${prefix}-${newKey.toLowerCase().replace(/_/g, '-')}`
cssVars[varName] = value as string
}
})
}
flatten(colors)
return cssVars
}
export default colors
+81
View File
@@ -0,0 +1,81 @@
/**
* Main brand configuration for Hanzo AI
*/
export const brandConfig = {
// Company Information
company: {
name: 'Hanzo',
legalName: 'Hanzo AI Inc.',
tagline: 'Building the future of AI infrastructure',
description: 'Enterprise AI infrastructure and tools for the next generation of intelligent applications.',
founded: 2023,
headquarters: 'San Francisco, CA',
},
// Brand Values
values: [
'Innovation',
'Reliability',
'Transparency',
'Performance',
'Accessibility'
],
// Contact Information
contact: {
email: '[email protected]',
support: '[email protected]',
sales: '[email protected]',
press: '[email protected]',
},
// Social Media
social: {
twitter: 'https://twitter.com/hanzoai',
github: 'https://github.com/hanzoai',
linkedin: 'https://linkedin.com/company/hanzoai',
discord: 'https://discord.gg/hanzo',
youtube: 'https://youtube.com/@hanzoai',
},
// Domains
domains: {
main: 'https://hanzo.ai',
docs: 'https://docs.hanzo.ai',
api: 'https://api.hanzo.ai',
app: 'https://app.hanzo.ai',
brand: 'https://brand.hanzo.ai',
ui: 'https://ui.hanzo.ai',
status: 'https://status.hanzo.ai',
},
// Legal
legal: {
privacy: 'https://hanzo.ai/privacy',
terms: 'https://hanzo.ai/terms',
cookies: 'https://hanzo.ai/cookies',
security: 'https://hanzo.ai/security',
},
// SEO Defaults
seo: {
title: 'Hanzo AI - Enterprise AI Infrastructure',
description: 'Build, deploy, and scale AI applications with Hanzo\'s enterprise-grade infrastructure and tools.',
keywords: [
'AI infrastructure',
'machine learning',
'artificial intelligence',
'LLM',
'enterprise AI',
'AI platform',
'ML ops',
'AI deployment',
],
image: 'https://hanzo.ai/og-image.png',
twitterCard: 'summary_large_image',
twitterCreator: '@hanzoai',
},
} as const
export type BrandConfig = typeof brandConfig
+30
View File
@@ -0,0 +1,30 @@
/**
* @hanzo/brand - Official brand assets and design system for Hanzo AI
*/
export * from './colors'
export * from './typography'
export * from './logos'
export * from './tokens'
export * from './themes'
export * from './utils'
// Main brand configuration
export { brandConfig } from './config'
// Quick access exports
export { default as colors } from './colors'
export { default as typography } from './typography'
export { default as logos } from './logos'
export { default as tokens } from './tokens'
// Theme management
import { applyTheme } from './themes'
export { applyTheme }
// Default initialization
if (typeof window !== 'undefined') {
// Auto-apply theme based on system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
applyTheme(prefersDark ? 'dark' : 'light')
}
+161
View File
@@ -0,0 +1,161 @@
/**
* Hanzo brand logos and logo components
*/
import React from 'react'
// Logo URLs
export const logos = {
full: {
dark: {
svg: '/assets/logo/hanzo-full-dark.svg',
png: '/assets/logo/hanzo-full-dark.png',
},
light: {
svg: '/assets/logo/hanzo-full-light.svg',
png: '/assets/logo/hanzo-full-light.png',
},
},
mark: {
dark: {
svg: '/assets/logo/hanzo-mark-dark.svg',
png: '/assets/logo/hanzo-mark-dark.png',
},
light: {
svg: '/assets/logo/hanzo-mark-light.svg',
png: '/assets/logo/hanzo-mark-light.png',
},
},
horizontal: {
dark: {
svg: '/assets/logo/hanzo-horizontal-dark.svg',
png: '/assets/logo/hanzo-horizontal-dark.png',
},
light: {
svg: '/assets/logo/hanzo-horizontal-light.svg',
png: '/assets/logo/hanzo-horizontal-light.png',
},
},
stacked: {
dark: {
svg: '/assets/logo/hanzo-stacked-dark.svg',
png: '/assets/logo/hanzo-stacked-dark.png',
},
light: {
svg: '/assets/logo/hanzo-stacked-light.svg',
png: '/assets/logo/hanzo-stacked-light.png',
},
},
} as const
// Logo component props
export interface HanzoLogoProps {
variant?: 'full' | 'mark' | 'horizontal' | 'stacked'
size?: 'small' | 'medium' | 'large' | 'xl' | number
theme?: 'light' | 'dark' | 'auto'
className?: string
style?: React.CSSProperties
}
// Size mappings
const sizeMap = {
small: 24,
medium: 32,
large: 48,
xl: 64,
}
// Logo React component
export function HanzoLogo({
variant = 'full',
size = 'medium',
theme = 'auto',
className = '',
style = {},
}: HanzoLogoProps) {
const height = typeof size === 'number' ? size : sizeMap[size]
// Auto theme detection
const actualTheme = theme === 'auto'
? (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches)
? 'dark'
: 'light'
: theme
const logoSrc = logos[variant][actualTheme].svg
return (
<img
src={logoSrc}
alt="Hanzo Logo"
height={height}
className={`hanzo-logo hanzo-logo--${variant} hanzo-logo--${actualTheme} ${className}`}
style={{
height: `${height}px`,
width: 'auto',
...style,
}}
/>
)
}
// SVG Logo component (inline)
export function HanzoLogoSVG({
size = 32,
color = 'currentColor',
className = '',
}: {
size?: number
color?: string
className?: string
}) {
return (
<svg
width={size}
height={size}
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M8 8H24V24H8V8Z"
fill={color}
/>
<path
d="M12 12H20V20H12V12Z"
fill="white"
fillOpacity="0.2"
/>
<path
d="M14 4L18 8H14V4Z"
fill={color}
/>
<path
d="M18 28L14 24H18V28Z"
fill={color}
/>
<path
d="M4 14L8 18V14H4Z"
fill={color}
/>
<path
d="M28 18L24 14V18H28Z"
fill={color}
/>
</svg>
)
}
// Logo favicon component
export function HanzoFavicon({ size = 16 }: { size?: number }) {
return (
<link
rel="icon"
type="image/svg+xml"
href={`data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 ${size} ${size}'><rect width='${size}' height='${size}' fill='%230A0A0B'/><rect x='${size * 0.25}' y='${size * 0.25}' width='${size * 0.5}' height='${size * 0.5}' fill='white' opacity='0.2'/></svg>`}
/>
)
}
export default logos
+133
View File
@@ -0,0 +1,133 @@
/**
* Theme management for Hanzo brand
*/
import { colors } from './colors'
import { getCSSVariables } from './colors'
export interface Theme {
name: string
colors: typeof colors
cssVariables: Record<string, string>
}
// Light theme
export const lightTheme: Theme = {
name: 'light',
colors: colors,
cssVariables: getCSSVariables('hanzo'),
}
// Dark theme colors (modified)
const darkColors = {
...colors,
primary: {
...colors.primary,
DEFAULT: '#FFFFFF',
},
background: {
DEFAULT: '#0A0A0B',
secondary: '#171717',
tertiary: '#262626',
dark: '#FFFFFF',
darkSecondary: '#FAFAFA',
darkTertiary: '#F5F5F5',
},
text: {
primary: '#FFFFFF',
secondary: '#A3A3A3',
tertiary: '#737373',
disabled: '#525252',
inverse: '#0A0A0B',
link: '#60A5FA',
linkHover: '#93C5FD',
},
border: {
DEFAULT: '#262626',
light: '#171717',
dark: '#404040',
focus: '#60A5FA',
},
}
export const darkTheme: Theme = {
name: 'dark',
colors: darkColors,
cssVariables: getCSSVariables('hanzo'),
}
// Available themes
export const themes = {
light: lightTheme,
dark: darkTheme,
} as const
// Apply theme to document
export function applyTheme(theme: keyof typeof themes | Theme = 'light') {
if (typeof window === 'undefined') return
const themeConfig = typeof theme === 'string' ? themes[theme] : theme
const root = document.documentElement
// Apply CSS variables
Object.entries(themeConfig.cssVariables).forEach(([key, value]) => {
root.style.setProperty(key, value)
})
// Set theme attribute
root.setAttribute('data-theme', themeConfig.name)
// Store preference
localStorage.setItem('hanzo-theme', themeConfig.name)
}
// Get current theme
export function getCurrentTheme(): Theme {
if (typeof window === 'undefined') return lightTheme
const stored = localStorage.getItem('hanzo-theme')
if (stored && stored in themes) {
return themes[stored as keyof typeof themes]
}
// Check system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
return prefersDark ? darkTheme : lightTheme
}
// Create custom theme
export function createTheme(config: Partial<Theme>): Theme {
return {
name: config.name || 'custom',
colors: config.colors || colors,
cssVariables: config.cssVariables || getCSSVariables('hanzo'),
}
}
// Theme switcher hook for React
export function useTheme() {
const [theme, setThemeState] = React.useState<Theme>(getCurrentTheme)
React.useEffect(() => {
const handleChange = (e: MediaQueryListEvent) => {
const newTheme = e.matches ? darkTheme : lightTheme
setThemeState(newTheme)
applyTheme(newTheme)
}
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
mediaQuery.addEventListener('change', handleChange)
return () => mediaQuery.removeEventListener('change', handleChange)
}, [])
const setTheme = (theme: keyof typeof themes | Theme) => {
const themeConfig = typeof theme === 'string' ? themes[theme] : theme
setThemeState(themeConfig)
applyTheme(themeConfig)
}
return { theme, setTheme }
}
export default themes
+162
View File
@@ -0,0 +1,162 @@
/**
* Design tokens for Hanzo brand
*/
export const tokens = {
// Spacing scale
spacing: {
0: '0',
px: '1px',
0.5: '0.125rem', // 2px
1: '0.25rem', // 4px
1.5: '0.375rem', // 6px
2: '0.5rem', // 8px
2.5: '0.625rem', // 10px
3: '0.75rem', // 12px
3.5: '0.875rem', // 14px
4: '1rem', // 16px
5: '1.25rem', // 20px
6: '1.5rem', // 24px
7: '1.75rem', // 28px
8: '2rem', // 32px
9: '2.25rem', // 36px
10: '2.5rem', // 40px
11: '2.75rem', // 44px
12: '3rem', // 48px
14: '3.5rem', // 56px
16: '4rem', // 64px
20: '5rem', // 80px
24: '6rem', // 96px
28: '7rem', // 112px
32: '8rem', // 128px
36: '9rem', // 144px
40: '10rem', // 160px
44: '11rem', // 176px
48: '12rem', // 192px
52: '13rem', // 208px
56: '14rem', // 224px
60: '15rem', // 240px
64: '16rem', // 256px
72: '18rem', // 288px
80: '20rem', // 320px
96: '24rem', // 384px
},
// Border radius
radius: {
none: '0',
sm: '0.125rem', // 2px
DEFAULT: '0.25rem', // 4px
md: '0.375rem', // 6px
lg: '0.5rem', // 8px
xl: '0.75rem', // 12px
'2xl': '1rem', // 16px
'3xl': '1.5rem', // 24px
full: '9999px',
},
// Box shadows
shadow: {
none: 'none',
sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
'2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
},
// Blur values
blur: {
none: '0',
sm: '4px',
DEFAULT: '8px',
md: '12px',
lg: '16px',
xl: '24px',
'2xl': '40px',
'3xl': '64px',
},
// Transitions
transition: {
duration: {
75: '75ms',
100: '100ms',
150: '150ms',
200: '200ms',
300: '300ms',
500: '500ms',
700: '700ms',
1000: '1000ms',
},
timing: {
DEFAULT: 'cubic-bezier(0.4, 0, 0.2, 1)',
linear: 'linear',
in: 'cubic-bezier(0.4, 0, 1, 1)',
out: 'cubic-bezier(0, 0, 0.2, 1)',
inOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
},
},
// Z-index scale
zIndex: {
0: '0',
10: '10',
20: '20',
30: '30',
40: '40',
50: '50',
dropdown: '100',
sticky: '200',
overlay: '300',
modal: '400',
popover: '500',
tooltip: '600',
notification: '700',
},
// Breakpoints
breakpoints: {
xs: '480px',
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
},
// Aspect ratios
aspectRatio: {
square: '1 / 1',
video: '16 / 9',
photo: '4 / 3',
portrait: '3 / 4',
ultrawide: '21 / 9',
},
// Animation
animation: {
spin: 'spin 1s linear infinite',
ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
bounce: 'bounce 1s infinite',
fadeIn: 'fadeIn 0.2s ease-in',
fadeOut: 'fadeOut 0.2s ease-out',
slideUp: 'slideUp 0.3s ease-out',
slideDown: 'slideDown 0.3s ease-out',
},
// Container
container: {
xs: '480px',
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
},
} as const
export default tokens
+176
View File
@@ -0,0 +1,176 @@
/**
* Hanzo brand typography
*/
export const typography = {
// Font families
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
serif: ['Georgia', 'Cambria', 'Times New Roman', 'Times', 'serif'],
mono: ['JetBrains Mono', 'Fira Code', 'SF Mono', 'Monaco', 'Consolas', 'monospace'],
display: ['Inter Display', 'Inter', 'system-ui', 'sans-serif'],
},
// Font sizes
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.75rem' }],
'2xl': ['1.5rem', { lineHeight: '2rem' }],
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
'5xl': ['3rem', { lineHeight: '1' }],
'6xl': ['3.75rem', { lineHeight: '1' }],
'7xl': ['4.5rem', { lineHeight: '1' }],
'8xl': ['6rem', { lineHeight: '1' }],
'9xl': ['8rem', { lineHeight: '1' }],
},
// Font weights
fontWeight: {
thin: '100',
extralight: '200',
light: '300',
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
extrabold: '800',
black: '900',
},
// Line heights
lineHeight: {
none: '1',
tight: '1.25',
snug: '1.375',
normal: '1.5',
relaxed: '1.625',
loose: '2',
},
// Letter spacing
letterSpacing: {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0em',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
},
// Text styles
heading: {
h1: {
fontSize: '3rem',
fontWeight: '700',
lineHeight: '1',
letterSpacing: '-0.025em',
},
h2: {
fontSize: '2.25rem',
fontWeight: '700',
lineHeight: '1.2',
letterSpacing: '-0.025em',
},
h3: {
fontSize: '1.875rem',
fontWeight: '600',
lineHeight: '1.25',
letterSpacing: '-0.025em',
},
h4: {
fontSize: '1.5rem',
fontWeight: '600',
lineHeight: '1.375',
letterSpacing: '0',
},
h5: {
fontSize: '1.25rem',
fontWeight: '600',
lineHeight: '1.5',
letterSpacing: '0',
},
h6: {
fontSize: '1.125rem',
fontWeight: '600',
lineHeight: '1.5',
letterSpacing: '0',
},
},
body: {
large: {
fontSize: '1.125rem',
fontWeight: '400',
lineHeight: '1.75',
},
base: {
fontSize: '1rem',
fontWeight: '400',
lineHeight: '1.5',
},
small: {
fontSize: '0.875rem',
fontWeight: '400',
lineHeight: '1.5',
},
tiny: {
fontSize: '0.75rem',
fontWeight: '400',
lineHeight: '1.5',
},
},
// Code styles
code: {
inline: {
fontFamily: 'JetBrains Mono, monospace',
fontSize: '0.875em',
fontWeight: '500',
backgroundColor: '#F5F5F5',
padding: '0.125rem 0.25rem',
borderRadius: '0.25rem',
},
block: {
fontFamily: 'JetBrains Mono, monospace',
fontSize: '0.875rem',
fontWeight: '400',
lineHeight: '1.5',
backgroundColor: '#F5F5F5',
padding: '1rem',
borderRadius: '0.5rem',
},
},
} as const
// Font face declarations for custom fonts
export const fontFaces = `
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('/fonts/inter-var.woff2') format('woff2-variations');
}
@font-face {
font-family: 'Inter Display';
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('/fonts/inter-display-var.woff2') format('woff2-variations');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 100 800;
font-display: swap;
src: url('/fonts/jetbrains-mono-var.woff2') format('woff2-variations');
}
`
export default typography
+181
View File
@@ -0,0 +1,181 @@
/**
* Utility functions for brand usage
*/
import { colors } from './colors'
import { tokens } from './tokens'
/**
* Convert hex color to RGB
*/
export function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null
}
/**
* Convert RGB to hex color
*/
export function rgbToHex(r: number, g: number, b: number): string {
return '#' + [r, g, b].map(x => {
const hex = x.toString(16)
return hex.length === 1 ? '0' + hex : hex
}).join('')
}
/**
* Get contrast ratio between two colors
*/
export function getContrastRatio(color1: string, color2: string): number {
const rgb1 = hexToRgb(color1)
const rgb2 = hexToRgb(color2)
if (!rgb1 || !rgb2) return 0
const l1 = getLuminance(rgb1)
const l2 = getLuminance(rgb2)
const lighter = Math.max(l1, l2)
const darker = Math.min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
/**
* Get relative luminance of a color
*/
function getLuminance({ r, g, b }: { r: number; g: number; b: number }): number {
const [rs, gs, bs] = [r, g, b].map(c => {
c = c / 255
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
})
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}
/**
* Check if color meets WCAG AA contrast requirements
*/
export function meetsContrastAA(foreground: string, background: string): boolean {
return getContrastRatio(foreground, background) >= 4.5
}
/**
* Check if color meets WCAG AAA contrast requirements
*/
export function meetsContrastAAA(foreground: string, background: string): boolean {
return getContrastRatio(foreground, background) >= 7
}
/**
* Generate color palette from base color
*/
export function generatePalette(baseColor: string, name = 'custom') {
const rgb = hexToRgb(baseColor)
if (!rgb) return null
const palette: Record<number, string> = {}
const steps = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
steps.forEach((step, index) => {
const factor = 1 - (index / steps.length)
const r = Math.round(rgb.r + (255 - rgb.r) * factor)
const g = Math.round(rgb.g + (255 - rgb.g) * factor)
const b = Math.round(rgb.b + (255 - rgb.b) * factor)
palette[step] = rgbToHex(r, g, b)
})
return palette
}
/**
* Get spacing value
*/
export function spacing(value: keyof typeof tokens.spacing): string {
return tokens.spacing[value]
}
/**
* Get radius value
*/
export function radius(value: keyof typeof tokens.radius): string {
return tokens.radius[value]
}
/**
* Get shadow value
*/
export function shadow(value: keyof typeof tokens.shadow): string {
return tokens.shadow[value]
}
/**
* Class name utility (similar to clsx)
*/
export function cn(...classes: (string | undefined | null | false)[]): string {
return classes.filter(Boolean).join(' ')
}
/**
* Generate Open Graph image URL
*/
export function getOgImageUrl(params: {
title: string
description?: string
logo?: boolean
theme?: 'light' | 'dark'
}): string {
const baseUrl = 'https://hanzo.ai/api/og'
const searchParams = new URLSearchParams({
title: params.title,
...(params.description && { description: params.description }),
...(params.logo !== false && { logo: 'true' }),
theme: params.theme || 'light',
})
return `${baseUrl}?${searchParams.toString()}`
}
/**
* Load brand fonts
*/
export function loadFonts() {
if (typeof window === 'undefined') return
const link = document.createElement('link')
link.rel = 'preconnect'
link.href = 'https://fonts.googleapis.com'
document.head.appendChild(link)
const link2 = document.createElement('link')
link2.rel = 'preconnect'
link2.href = 'https://fonts.gstatic.com'
link2.crossOrigin = 'anonymous'
document.head.appendChild(link2)
const fontLink = document.createElement('link')
fontLink.rel = 'stylesheet'
fontLink.href = 'https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap'
document.head.appendChild(fontLink)
}
export default {
hexToRgb,
rgbToHex,
getContrastRatio,
meetsContrastAA,
meetsContrastAAA,
generatePalette,
spacing,
radius,
shadow,
cn,
getOgImageUrl,
loadFonts,
}
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react",
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "build"]
}