mirror of
https://github.com/hanzoai/brand.git
synced 2026-10-23 03:05:28 +00:00
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:
+47
@@ -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/
|
||||
@@ -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**
|
||||
|
||||
[](https://www.npmjs.com/package/@hanzo/brand)
|
||||
[](LICENSE)
|
||||
[](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>
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
Reference in New Issue
Block a user