diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml index 163dd64b..64414544 100644 --- a/.github/workflows/deploy-gh-pages.yml +++ b/.github/workflows/deploy-gh-pages.yml @@ -125,6 +125,12 @@ jobs: HTMLEOF + - name: Inject no-cache headers for staging + run: | + # Add no-cache meta tags to all HTML files for staging environment + find dist -name "*.html" -exec sed -i 's//\n \n \n /' {} \; + echo "Injected no-cache headers into $(find dist -name '*.html' | wc -l) HTML files" + - name: Setup Pages uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 diff --git a/app.json b/app.json index 9f16d81b..35e65614 100644 --- a/app.json +++ b/app.json @@ -48,7 +48,8 @@ ] ], "experiments": { - "typedRoutes": true + "typedRoutes": true, + "baseUrl": "/thumbcode" }, "extra": { "router": { diff --git a/app/(onboarding)/api-keys.tsx b/app/(onboarding)/api-keys.tsx index fb3ca8b0..29da84aa 100644 --- a/app/(onboarding)/api-keys.tsx +++ b/app/(onboarding)/api-keys.tsx @@ -2,6 +2,7 @@ * API Keys Screen * * Collects AI provider API keys (Anthropic/OpenAI). + * Uses paint daube icons for brand consistency. */ import { CredentialService } from '@thumbcode/core/src/credentials/CredentialService'; @@ -10,6 +11,7 @@ import { useState } from 'react'; import { ActivityIndicator, Pressable, ScrollView, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { StepsProgress } from '@/components/feedback'; +import { CloseIcon, LightbulbIcon, SecurityIcon, SuccessIcon } from '@/components/icons'; import { Container, VStack } from '@/components/layout'; import { Input, Text } from '@/components/ui'; @@ -129,7 +131,9 @@ export default function ApiKeysScreen() { }} > - ๐Ÿ” + + + Your Keys, Your Device @@ -147,8 +151,12 @@ export default function ApiKeysScreen() { Anthropic (Claude) {anthropicKey.isValidating && } - {anthropicKey.isValid === true && โœ“} - {anthropicKey.isValid === false && โœ•} + {anthropicKey.isValid === true && ( + + )} + {anthropicKey.isValid === false && ( + + )} {openaiKey.isValidating && } - {openaiKey.isValid === true && โœ“} - {openaiKey.isValid === false && โœ•} + {openaiKey.isValid === true && ( + + )} + {openaiKey.isValid === false && ( + + )} - - ๐Ÿ’ก Tip: You can add more providers later in - Settings. At least one key is recommended to enable AI agents. - + + + + + + Tip: You can add more providers later in + Settings. At least one key is recommended to enable AI agents. + + diff --git a/app/(onboarding)/complete.tsx b/app/(onboarding)/complete.tsx index 93bf2219..ae8d1cdd 100644 --- a/app/(onboarding)/complete.tsx +++ b/app/(onboarding)/complete.tsx @@ -2,21 +2,61 @@ * Complete Screen * * Final onboarding screen - celebrates completion and launches main app. + * Uses paint daube icons for brand consistency. */ import { useRouter } from 'expo-router'; +import type React from 'react'; import { useEffect, useRef } from 'react'; import { Animated, Pressable, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { + AgentIcon, + CelebrateIcon, + ChatIcon, + type IconColor, + MobileIcon, + SuccessIcon, + TasksIcon, +} from '@/components/icons'; import { Container, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; import { useOnboarding } from '@/contexts/onboarding'; -const CAPABILITIES = [ - { icon: '๐Ÿค–', title: 'AI Agent Teams', description: 'Multi-agent collaboration ready' }, - { icon: '๐Ÿ“ฑ', title: 'Mobile Git', description: 'Clone, commit, push from your phone' }, - { icon: '๐Ÿ’ฌ', title: 'Real-time Chat', description: 'Direct agent communication' }, - { icon: '๐Ÿ“Š', title: 'Progress Tracking', description: 'Monitor tasks and metrics' }, +type CapabilityIcon = React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; + +interface Capability { + Icon: CapabilityIcon; + iconColor: IconColor; + title: string; + description: string; +} + +const CAPABILITIES: Capability[] = [ + { + Icon: AgentIcon, + iconColor: 'coral', + title: 'AI Agent Teams', + description: 'Multi-agent collaboration ready', + }, + { + Icon: MobileIcon, + iconColor: 'teal', + title: 'Mobile Git', + description: 'Clone, commit, push from your phone', + }, + { + Icon: ChatIcon, + iconColor: 'gold', + title: 'Real-time Chat', + description: 'Direct agent communication', + }, + { + Icon: TasksIcon, + iconColor: 'teal', + title: 'Progress Tracking', + description: 'Monitor tasks and metrics', + }, ]; export default function CompleteScreen() { @@ -69,7 +109,7 @@ export default function CompleteScreen() { borderBottomLeftRadius: 32, }} > - ๐ŸŽ‰ + @@ -108,7 +148,7 @@ export default function CompleteScreen() { borderBottomLeftRadius: 10, }} > - {cap.icon} + @@ -118,7 +158,7 @@ export default function CompleteScreen() { {cap.description} - โœ“ + ))} diff --git a/app/(onboarding)/create-project.tsx b/app/(onboarding)/create-project.tsx index d578cca2..8894d8f1 100644 --- a/app/(onboarding)/create-project.tsx +++ b/app/(onboarding)/create-project.tsx @@ -2,6 +2,7 @@ * Create Project Screen * * Helps user create their first project by connecting a repository. + * Uses paint daube icons for brand consistency. */ import { CredentialService } from '@thumbcode/core/src/credentials/CredentialService'; @@ -11,6 +12,7 @@ import { useState } from 'react'; import { ActivityIndicator, Pressable, ScrollView, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { StepsProgress } from '@/components/feedback'; +import { FolderIcon, SecurityIcon, StarIcon, SuccessIcon } from '@/components/icons'; import { Container, VStack } from '@/components/layout'; import { Input, Text } from '@/components/ui'; @@ -159,18 +161,36 @@ export default function CreateProjectScreen() { }} > - {repo.isPrivate ? '๐Ÿ”’' : '๐Ÿ“‚'} + + {repo.isPrivate ? ( + + ) : ( + + )} + {repo.name} - {selectedRepo?.id === repo.id && โœ“} + {selectedRepo?.id === repo.id && ( + + )} {repo.description} - - {repo.fullName} {repo.stars > 0 && `โญ ${repo.stars}`} - + + + {repo.fullName} + + {repo.stars > 0 && ( + + + + {repo.stars} + + + )} + ))} diff --git a/app/(onboarding)/github-auth.tsx b/app/(onboarding)/github-auth.tsx index 941dfa0a..aa5d7f9c 100644 --- a/app/(onboarding)/github-auth.tsx +++ b/app/(onboarding)/github-auth.tsx @@ -2,6 +2,7 @@ * GitHub Auth Screen * * Guides user through GitHub Device Flow authentication. + * Uses paint daube icons for brand consistency. */ import { useRouter } from 'expo-router'; @@ -9,6 +10,7 @@ import { useState } from 'react'; import { ActivityIndicator, Linking, Pressable, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { StepsProgress } from '@/components/feedback'; +import { LinkIcon, SuccessIcon } from '@/components/icons'; import { Container, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; @@ -80,7 +82,9 @@ export default function GitHubAuthScreen() { borderBottomLeftRadius: 12, }} > - ๐Ÿ”— + + + Secure Device Flow @@ -184,7 +188,9 @@ export default function GitHubAuthScreen() { borderBottomLeftRadius: 12, }} > - โœ“ + + + GitHub Connected! diff --git a/app/(onboarding)/welcome.tsx b/app/(onboarding)/welcome.tsx index 8332e3c0..90fcff39 100644 --- a/app/(onboarding)/welcome.tsx +++ b/app/(onboarding)/welcome.tsx @@ -2,37 +2,75 @@ * Welcome Screen * * First screen of onboarding flow. Introduces ThumbCode and its features. + * Uses procedural paint daube icons for brand consistency. */ import { useRouter } from 'expo-router'; import { Pressable, ScrollView, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { + AgentIcon, + type IconVariant, + LightningIcon, + MobileIcon, + SecurityIcon, + ThumbIcon, +} from '@/components/icons'; import { VStack } from '@/components/layout'; import { Text } from '@/components/ui'; -const FEATURES = [ +interface Feature { + icon: IconVariant; + title: string; + description: string; + color: 'coral' | 'teal' | 'gold'; +} + +const FEATURES: Feature[] = [ { - icon: '๐Ÿค–', + icon: 'agent', title: 'AI Agent Teams', description: 'Architect, Implementer, Reviewer, and Tester agents work in parallel', + color: 'coral', }, { - icon: '๐Ÿ“ฑ', + icon: 'mobile', title: 'Mobile-First Git', description: 'Full git workflow from your phone with isomorphic-git', + color: 'teal', }, { - icon: '๐Ÿ”', + icon: 'security', title: 'Your Keys, Your Control', description: 'API keys never leave your device - stored in secure hardware', + color: 'gold', }, { - icon: 'โšก', + icon: 'lightning', title: 'Zero Server Costs', description: 'Bring your own keys - no subscriptions, no vendor lock-in', + color: 'coral', }, ]; +/** Feature icon component that renders the appropriate paint daube icon */ +function FeatureIcon({ variant, color }: { variant: IconVariant; color: Feature['color'] }) { + const iconProps = { size: 28, color, turbulence: 0.25 }; + + switch (variant) { + case 'agent': + return ; + case 'mobile': + return ; + case 'security': + return ; + case 'lightning': + return ; + default: + return ; + } +} + export default function WelcomeScreen() { const router = useRouter(); const insets = useSafeAreaInsets(); @@ -55,7 +93,7 @@ export default function WelcomeScreen() { borderBottomLeftRadius: 20, }} > - ๐Ÿ‘ + @@ -69,7 +107,7 @@ export default function WelcomeScreen() { {/* Features */} - {FEATURES.map((feature) => ( + {FEATURES.map((feature, _index) => ( - {feature.icon} + + + {feature.title} diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 2ef52419..b9f12c72 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -2,22 +2,34 @@ * Tab Layout * * Main tab navigation for the app. + * Uses paint daube icons for brand consistency. */ import { Tabs } from 'expo-router'; +import type React from 'react'; import { Text, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { + AgentIcon, + ChatIcon, + FolderIcon, + HomeIcon, + type IconColor, + SettingsIcon, +} from '@/components/icons'; interface TabIconProps { - icon: string; + Icon: React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; label: string; focused: boolean; } -function TabIcon({ icon, label, focused }: TabIconProps) { +function TabIcon({ Icon, label, focused }: TabIconProps) { return ( - {icon} + + + @@ -63,35 +75,41 @@ export default function TabLayout() { options={{ title: 'Home', headerTitle: 'ThumbCode', - tabBarIcon: ({ focused }) => , + tabBarIcon: ({ focused }) => , }} /> , + tabBarIcon: ({ focused }) => ( + + ), }} /> , + tabBarIcon: ({ focused }) => ( + + ), }} /> , + tabBarIcon: ({ focused }) => , }} /> , + tabBarIcon: ({ focused }) => ( + + ), }} /> diff --git a/app/(tabs)/agents.tsx b/app/(tabs)/agents.tsx index e6358daf..1e25dd47 100644 --- a/app/(tabs)/agents.tsx +++ b/app/(tabs)/agents.tsx @@ -2,25 +2,49 @@ * Agents Screen * * Dashboard showing all AI agents, their status, and metrics. + * Uses paint daube icons for brand consistency. */ import { useRouter } from 'expo-router'; +import type React from 'react'; import { useState } from 'react'; import { Pressable, ScrollView, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { StatusBadge } from '@/components/display'; import { ProgressBar } from '@/components/feedback'; +import { + AgentIcon, + type IconColor, + LightningIcon, + ReviewIcon, + SearchIcon, + StarIcon, + SuccessIcon, +} from '@/components/icons'; import { Container, HStack, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; +/** Agent avatar icon component */ +type AgentAvatarIcon = React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; + // Mock data -const MOCK_AGENTS = [ +const MOCK_AGENTS: Array<{ + id: string; + name: string; + role: 'architect' | 'implementer' | 'reviewer' | 'tester'; + status: 'idle' | 'coding' | 'reviewing' | 'error' | 'waiting_approval'; + AvatarIcon: AgentAvatarIcon; + avatarColor: IconColor; + currentTask: { title: string; progress: number } | null; + metrics: { tasksCompleted: number; successRate: number; avgTaskTime: number }; +}> = [ { id: '1', name: 'Architect', role: 'architect' as const, status: 'idle' as const, - avatar: '๐Ÿ›๏ธ', + AvatarIcon: StarIcon, + avatarColor: 'gold', currentTask: null, metrics: { tasksCompleted: 47, @@ -33,7 +57,8 @@ const MOCK_AGENTS = [ name: 'Implementer', role: 'implementer' as const, status: 'coding' as const, - avatar: 'โšก', + AvatarIcon: LightningIcon, + avatarColor: 'teal', currentTask: { title: 'Add user authentication', progress: 65, @@ -49,7 +74,8 @@ const MOCK_AGENTS = [ name: 'Reviewer', role: 'reviewer' as const, status: 'reviewing' as const, - avatar: '๐Ÿ”', + AvatarIcon: ReviewIcon, + avatarColor: 'coral', currentTask: { title: 'Review PR #42', progress: 30, @@ -65,7 +91,8 @@ const MOCK_AGENTS = [ name: 'Tester', role: 'tester' as const, status: 'idle' as const, - avatar: '๐Ÿงช', + AvatarIcon: SearchIcon, + avatarColor: 'teal', currentTask: null, metrics: { tasksCompleted: 72, @@ -136,7 +163,9 @@ export default function AgentsScreen() { borderBottomLeftRadius: 10, }} > - ๐Ÿค– + + + {activeAgents}/{MOCK_AGENTS.length} @@ -154,7 +183,9 @@ export default function AgentsScreen() { borderBottomLeftRadius: 10, }} > - โœ“ + + + {totalTasks} @@ -230,7 +261,7 @@ export default function AgentsScreen() { borderBottomLeftRadius: 12, }} > - {agent.avatar} + diff --git a/app/(tabs)/chat.tsx b/app/(tabs)/chat.tsx index 6c652d6e..42f31e82 100644 --- a/app/(tabs)/chat.tsx +++ b/app/(tabs)/chat.tsx @@ -3,12 +3,23 @@ * * Main chat interface for human-agent communication. * Uses simplified mock components for demonstration. + * Uses paint daube icons for brand consistency. */ +import type React from 'react'; import { useRef, useState } from 'react'; import { FlatList, KeyboardAvoidingView, Platform, Pressable, TextInput, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Badge } from '@/components/display'; +import { + EditIcon, + type IconColor, + LightningIcon, + ReviewIcon, + SearchIcon, + StarIcon, + UserIcon, +} from '@/components/icons'; import { HStack, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; @@ -66,22 +77,45 @@ const MOCK_MESSAGES: MockMessage[] = [ }, ]; +type AgentAvatarIcon = React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; + function getAgentInfo(sender: MockMessage['sender']): { name: string; - avatar: string; - color: string; + AvatarIcon: AgentAvatarIcon; + iconColor: IconColor; + textColor: string; } { switch (sender) { case 'architect': - return { name: 'Architect', avatar: '๐Ÿ›๏ธ', color: 'text-gold-400' }; + return { + name: 'Architect', + AvatarIcon: StarIcon, + iconColor: 'gold', + textColor: 'text-gold-400', + }; case 'implementer': - return { name: 'Implementer', avatar: 'โšก', color: 'text-teal-400' }; + return { + name: 'Implementer', + AvatarIcon: LightningIcon, + iconColor: 'teal', + textColor: 'text-teal-400', + }; case 'reviewer': - return { name: 'Reviewer', avatar: '๐Ÿ”', color: 'text-coral-400' }; + return { + name: 'Reviewer', + AvatarIcon: ReviewIcon, + iconColor: 'coral', + textColor: 'text-coral-400', + }; case 'tester': - return { name: 'Tester', avatar: '๐Ÿงช', color: 'text-purple-400' }; + return { + name: 'Tester', + AvatarIcon: SearchIcon, + iconColor: 'teal', + textColor: 'text-purple-400', + }; default: - return { name: 'You', avatar: '๐Ÿ‘ค', color: 'text-white' }; + return { name: 'You', AvatarIcon: UserIcon, iconColor: 'warmGray', textColor: 'text-white' }; } } @@ -102,9 +136,9 @@ function MockMessageBubble({ message }: { message: MockMessage }) { borderBottomLeftRadius: 6, }} > - {agentInfo.avatar} + - + {agentInfo.name} @@ -165,7 +199,7 @@ function MockApprovalCard({ > - ๐Ÿ“ + Ready to commit changes? diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 58815071..d9e2ee2d 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -2,13 +2,23 @@ * Home Screen (Dashboard) * * Main dashboard showing overview of projects, agents, and recent activity. + * Uses paint daube icons for brand consistency. */ import { useRouter } from 'expo-router'; +import type React from 'react'; import { Pressable, ScrollView, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Avatar, Badge } from '@/components/display'; import { ProgressBar } from '@/components/feedback'; +import { + AgentIcon, + BellIcon, + EditIcon, + FolderIcon, + SuccessIcon, + TasksIcon, +} from '@/components/icons'; import { Container, HStack, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; @@ -68,16 +78,18 @@ function getStatusColor(status: string): string { } } -function getActivityIcon(type: string): string { +/** Returns the appropriate icon component for activity type */ +function ActivityIcon({ type, size = 20 }: { type: string; size?: number }): React.ReactElement { + const iconProps = { size, turbulence: 0.2 }; switch (type) { case 'commit': - return '๐Ÿ“'; + return ; case 'review': - return 'โœ…'; + return ; case 'task': - return '๐Ÿ“‹'; + return ; default: - return '๐Ÿ””'; + return ; } } @@ -113,7 +125,9 @@ export default function HomeScreen() { borderBottomLeftRadius: 10, }} > - ๐Ÿ“ + + + {MOCK_STATS.activeProjects} @@ -131,7 +145,9 @@ export default function HomeScreen() { borderBottomLeftRadius: 10, }} > - ๐Ÿค– + + + {MOCK_STATS.runningAgents} @@ -149,7 +165,9 @@ export default function HomeScreen() { borderBottomLeftRadius: 10, }} > - ๐Ÿ“‹ + + + {MOCK_STATS.pendingTasks} @@ -264,7 +282,7 @@ export default function HomeScreen() { borderBottomLeftRadius: 8, }} > - {getActivityIcon(activity.type)} + diff --git a/app/(tabs)/projects.tsx b/app/(tabs)/projects.tsx index cc58ac85..5bbbaba0 100644 --- a/app/(tabs)/projects.tsx +++ b/app/(tabs)/projects.tsx @@ -2,6 +2,7 @@ * Projects Screen * * Lists all projects with quick actions and status. + * Uses paint daube icons for brand consistency. */ import { useRouter } from 'expo-router'; @@ -9,6 +10,14 @@ import { useState } from 'react'; import { Pressable, ScrollView, TextInput, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Badge, EmptyState } from '@/components/display'; +import { + AgentIcon, + BranchIcon, + FolderIcon, + GitIcon, + SearchIcon, + TasksIcon, +} from '@/components/icons'; import { Container, HStack, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; @@ -86,7 +95,9 @@ export default function ProjectsScreen() { borderBottomLeftRadius: 8, }} > - ๐Ÿ” + + + {filteredProjects.length === 0 ? ( - - ๐Ÿ“‚ - + {project.repository} - - ๐Ÿ”€ - + {project.branch} @@ -165,14 +173,20 @@ export default function ProjectsScreen() { {project.pendingTasks > 0 && ( - - ๐Ÿ“‹ {project.pendingTasks} tasks - + + + + {project.pendingTasks} tasks + + )} {project.activeAgents > 0 && ( - - ๐Ÿค– {project.activeAgents} agents - + + + + {project.activeAgents} agents + + )} diff --git a/app/(tabs)/settings.tsx b/app/(tabs)/settings.tsx index 316e5e44..38a8016f 100644 --- a/app/(tabs)/settings.tsx +++ b/app/(tabs)/settings.tsx @@ -2,17 +2,35 @@ * Settings Screen * * App settings, credential management, and preferences. + * Uses paint daube icons for brand consistency. */ import { useRouter } from 'expo-router'; +import type React from 'react'; import { Linking, Pressable, ScrollView, Switch, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Avatar, Badge } from '@/components/display'; +import { + AgentIcon, + BellIcon, + BookIcon, + BrainIcon, + BranchIcon, + type IconColor, + InfoIcon, + KeyboardIcon, + LegalIcon, + LinkIcon, + PaletteIcon, + SupportIcon, + VibrateIcon, +} from '@/components/icons'; import { Container, Divider, HStack, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; interface SettingsItemProps { - icon: string; + Icon: React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; + iconColor?: IconColor; title: string; subtitle?: string; value?: string; @@ -26,7 +44,8 @@ interface SettingsItemProps { } function SettingsItem({ - icon, + Icon, + iconColor = 'warmGray', title, subtitle, value, @@ -51,7 +70,7 @@ function SettingsItem({ borderBottomLeftRadius: 6, }} > - {icon} + @@ -145,7 +164,8 @@ export default function SettingsScreen() { router.push('/settings/credentials')} @@ -187,10 +208,16 @@ export default function SettingsScreen() { - {}} /> + {}} + /> router.push('/settings/editor')} /> @@ -236,14 +264,16 @@ export default function SettingsScreen() { router.push('/settings/agents')} /> router.push('/settings/agents')} @@ -270,24 +300,25 @@ export default function SettingsScreen() { Linking.openURL('https://thumbcode.dev/docs')} /> Linking.openURL('https://thumbcode.dev/support')} /> Linking.openURL('https://thumbcode.dev/legal')} /> - + diff --git a/app/+not-found.tsx b/app/+not-found.tsx index dbecf5a7..a5a29329 100644 --- a/app/+not-found.tsx +++ b/app/+not-found.tsx @@ -2,11 +2,13 @@ * Not Found Screen * * Displayed when navigating to a route that doesn't exist. + * Uses paint daube icons for brand consistency. */ import { Stack, useRouter } from 'expo-router'; import { Pressable, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { SearchIcon } from '@/components/icons'; import { Container, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; @@ -34,7 +36,7 @@ export default function NotFoundScreen() { borderBottomLeftRadius: 24, }} > - ๐Ÿ” + {/* Message */} diff --git a/app/agent/[id].tsx b/app/agent/[id].tsx index 2919d95d..f6271a77 100644 --- a/app/agent/[id].tsx +++ b/app/agent/[id].tsx @@ -2,6 +2,7 @@ * Agent Detail Screen * * Shows detailed agent information, metrics, task history, and controls. + * Uses paint daube icons for brand consistency. */ import { Stack, useLocalSearchParams, useRouter } from 'expo-router'; @@ -10,9 +11,20 @@ import { Pressable, ScrollView, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Badge, StatusBadge } from '@/components/display'; import { ProgressBar } from '@/components/feedback'; +import { + type IconColor, + LightningIcon, + ReviewIcon, + StarIcon, + SuccessIcon, + TestIcon, +} from '@/components/icons'; import { Container, HStack, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; +/** Agent avatar icon component type */ +type AgentAvatarIcon = React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; + // Mock data const MOCK_AGENTS: Record< string, @@ -21,7 +33,8 @@ const MOCK_AGENTS: Record< name: string; role: string; status: 'idle' | 'coding' | 'reviewing' | 'testing' | 'waiting_approval' | 'error'; - avatar: string; + AvatarIcon: AgentAvatarIcon; + avatarColor: IconColor; description: string; currentTask: { id: string; title: string; progress: number } | null; metrics: { @@ -44,7 +57,8 @@ const MOCK_AGENTS: Record< name: 'Architect', role: 'architect', status: 'idle', - avatar: '๐Ÿ›๏ธ', + AvatarIcon: StarIcon, + avatarColor: 'gold', description: 'Designs system architecture, creates task breakdowns, and coordinates agent workflows.', currentTask: null, @@ -83,7 +97,8 @@ const MOCK_AGENTS: Record< name: 'Implementer', role: 'implementer', status: 'coding', - avatar: 'โšก', + AvatarIcon: LightningIcon, + avatarColor: 'teal', description: 'Writes code, implements features, and resolves technical issues based on architectural plans.', currentTask: { @@ -133,7 +148,8 @@ const MOCK_AGENTS: Record< name: 'Reviewer', role: 'reviewer', status: 'reviewing', - avatar: '๐Ÿ”', + AvatarIcon: ReviewIcon, + avatarColor: 'coral', description: 'Reviews code changes, ensures quality standards, and provides feedback to implementers.', currentTask: { @@ -169,7 +185,8 @@ const MOCK_AGENTS: Record< name: 'Tester', role: 'tester', status: 'idle', - avatar: '๐Ÿงช', + AvatarIcon: TestIcon, + avatarColor: 'teal', description: 'Writes and runs tests, validates functionality, and reports bugs to the team.', currentTask: null, metrics: { @@ -277,7 +294,7 @@ export default function AgentDetailScreen() { borderBottomLeftRadius: 16, }} > - {agent.avatar} + @@ -451,7 +468,7 @@ export default function AgentDetailScreen() { {getCapabilities(agent.role).map((capability) => ( - โœ“ + {capability} ))} diff --git a/app/index.tsx b/app/index.tsx index 2f7bf627..0e7a65b2 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,4 +1,5 @@ import { ScrollView, View } from 'react-native'; +import { AgentIcon, MobileIcon, SecurityIcon } from '../src/components/icons'; import { Button, Card, Input, Text } from '../src/components/ui'; /** @@ -32,27 +33,36 @@ export default function Index() { - - ๐Ÿค– Multi-Agent Teams - + + + + Multi-Agent Teams + + Architect, Implementer, Reviewer, Tester agents working in parallel - - ๐Ÿ“ฑ Mobile-Native Git - + + + + Mobile-Native Git + + Full git workflow from your phone. Clone, commit, push โ€” powered by isomorphic-git - - ๐Ÿ”’ Credential Sovereignty - + + + + Credential Sovereignty + + Your API keys never leave your device. Stored in secure hardware enclaves diff --git a/app/settings/agents.tsx b/app/settings/agents.tsx index 7760f20c..55c56e5e 100644 --- a/app/settings/agents.tsx +++ b/app/settings/agents.tsx @@ -9,6 +9,7 @@ import { useState } from 'react'; import { Pressable, ScrollView, Switch, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Badge } from '@/components/display'; +import { LightbulbIcon } from '@/components/icons'; import { Container, Divider, HStack, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; @@ -284,7 +285,9 @@ export default function AgentSettingsScreen() { }} > - ๐Ÿ’ก + + + Tip diff --git a/app/settings/credentials.tsx b/app/settings/credentials.tsx index 9a86a53c..2cc702ab 100644 --- a/app/settings/credentials.tsx +++ b/app/settings/credentials.tsx @@ -9,11 +9,15 @@ import { useState } from 'react'; import { Alert, Pressable, ScrollView, TextInput, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Badge } from '@/components/display'; +import { type IconColor, LinkIcon, SecurityIcon } from '@/components/icons'; import { Container, Divider, HStack, VStack } from '@/components/layout'; import { Text } from '@/components/ui'; +type CredentialIconComponent = React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; + interface CredentialItemProps { - icon: string; + Icon: CredentialIconComponent; + iconColor?: IconColor; title: string; subtitle: string; isConnected: boolean; @@ -23,7 +27,8 @@ interface CredentialItemProps { } function CredentialItem({ - icon, + Icon, + iconColor = 'teal', title, subtitle, isConnected, @@ -43,7 +48,7 @@ function CredentialItem({ borderBottomLeftRadius: 8, }} > - {icon} + @@ -258,7 +263,8 @@ export default function CredentialsScreen() { - ๐Ÿ”’ + + + Secure Storage diff --git a/src/components/chat/ApprovalCard.tsx b/src/components/chat/ApprovalCard.tsx index e7668de4..7b48ae41 100644 --- a/src/components/chat/ApprovalCard.tsx +++ b/src/components/chat/ApprovalCard.tsx @@ -3,10 +3,23 @@ * * Displays approval requests from agents with approve/reject actions. * Uses organic styling with distinctive visual treatment for pending actions. + * Uses paint daube icons for brand consistency. */ import type { ApprovalMessage } from '@thumbcode/state'; -import { Pressable, Text, View } from 'react-native'; +import { Pressable, View } from 'react-native'; +import { + BranchIcon, + EditIcon, + FileIcon, + GitIcon, + type IconColor, + LightningIcon, +} from '@/components/icons'; +import { Text } from '@/components/ui'; + +/** Action icon component type */ +type ActionIconComponent = React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; interface ApprovalCardProps { message: ApprovalMessage; @@ -14,19 +27,28 @@ interface ApprovalCardProps { onReject: () => void; } +interface ActionInfo { + Icon: ActionIconComponent; + iconColor: IconColor; + label: string; + bgColor: string; +} + /** * Get action type icon and label */ -function getActionInfo(actionType: ApprovalMessage['metadata']['actionType']) { - const actionMap: Record< - ApprovalMessage['metadata']['actionType'], - { icon: string; label: string; color: string } - > = { - commit: { icon: '๐Ÿ“', label: 'Commit Changes', color: 'bg-teal-500' }, - push: { icon: 'โฌ†๏ธ', label: 'Push to Remote', color: 'bg-coral-500' }, - merge: { icon: '๐Ÿ”€', label: 'Merge Branch', color: 'bg-gold-500' }, - deploy: { icon: '๐Ÿš€', label: 'Deploy', color: 'bg-coral-600' }, - file_change: { icon: '๐Ÿ“„', label: 'File Changes', color: 'bg-teal-600' }, +function getActionInfo(actionType: ApprovalMessage['metadata']['actionType']): ActionInfo { + const actionMap: Record = { + commit: { Icon: EditIcon, iconColor: 'teal', label: 'Commit Changes', bgColor: 'bg-teal-500' }, + push: { Icon: GitIcon, iconColor: 'coral', label: 'Push to Remote', bgColor: 'bg-coral-500' }, + merge: { Icon: BranchIcon, iconColor: 'gold', label: 'Merge Branch', bgColor: 'bg-gold-500' }, + deploy: { Icon: LightningIcon, iconColor: 'coral', label: 'Deploy', bgColor: 'bg-coral-600' }, + file_change: { + Icon: FileIcon, + iconColor: 'teal', + label: 'File Changes', + bgColor: 'bg-teal-600', + }, }; return actionMap[actionType] || actionMap.commit; } @@ -51,8 +73,12 @@ export function ApprovalCard({ message, onApprove, onReject }: ApprovalCardProps > {/* Header */} - {actionInfo.icon} - {actionInfo.label} + + + + + {actionInfo.label} + {!isPending && ( ; + +interface FileIconInfo { + Icon: FileIconComponent; + color: IconColor; +} + +function getFileIconInfo(name: string): FileIconInfo { const ext = name.split('.').pop()?.toLowerCase(); - const iconMap: Record = { - ts: '๐Ÿ“˜', - tsx: 'โš›๏ธ', - js: '๐Ÿ“™', - jsx: 'โš›๏ธ', - json: '๐Ÿ“‹', - md: '๐Ÿ“', - css: '๐ŸŽจ', - scss: '๐ŸŽจ', - html: '๐ŸŒ', - png: '๐Ÿ–ผ๏ธ', - jpg: '๐Ÿ–ผ๏ธ', - svg: '๐Ÿ“', - git: '๐Ÿ”ง', - env: 'โš™๏ธ', - lock: '๐Ÿ”’', + const iconMap: Record = { + ts: { Icon: FileCodeIcon, color: 'teal' }, + tsx: { Icon: FileCodeIcon, color: 'teal' }, + js: { Icon: FileCodeIcon, color: 'gold' }, + jsx: { Icon: FileCodeIcon, color: 'gold' }, + json: { Icon: FileDataIcon, color: 'gold' }, + md: { Icon: FileDocIcon, color: 'warmGray' }, + css: { Icon: FileStyleIcon, color: 'coral' }, + scss: { Icon: FileStyleIcon, color: 'coral' }, + html: { Icon: FileWebIcon, color: 'teal' }, + png: { Icon: FileMediaIcon, color: 'coral' }, + jpg: { Icon: FileMediaIcon, color: 'coral' }, + jpeg: { Icon: FileMediaIcon, color: 'coral' }, + svg: { Icon: FileMediaIcon, color: 'coral' }, + gif: { Icon: FileMediaIcon, color: 'coral' }, + git: { Icon: FileConfigIcon, color: 'warmGray' }, + env: { Icon: FileConfigIcon, color: 'warmGray' }, + lock: { Icon: FileConfigIcon, color: 'warmGray' }, + yaml: { Icon: FileConfigIcon, color: 'warmGray' }, + yml: { Icon: FileConfigIcon, color: 'warmGray' }, }; - return iconMap[ext || ''] || '๐Ÿ“„'; + return iconMap[ext || ''] || { Icon: FileIcon, color: 'warmGray' }; } function getStatusColor(node: FileNode): string { @@ -117,7 +144,11 @@ function FileTreeNodeRow({ accessibilityLabel: string; accessibilityHint: string; }) { - const icon = isFolder ? (isExpanded ? '๐Ÿ“‚' : '๐Ÿ“') : getFileIcon(node.name); + // Get the appropriate icon based on file type or folder state + const iconInfo = isFolder + ? { Icon: isExpanded ? FolderOpenIcon : FolderIcon, color: 'gold' as IconColor } + : getFileIconInfo(node.name); + const rowClass = isSelected ? 'bg-teal-600/20' : 'active:bg-neutral-700'; const textClass = isSelected ? 'text-teal-300' : 'text-neutral-200'; @@ -135,7 +166,9 @@ function FileTreeNodeRow({ ) : ( )} - {icon} + + + {node.name} diff --git a/src/components/display/Badge.tsx b/src/components/display/Badge.tsx index 4d8200fc..07b9e099 100644 --- a/src/components/display/Badge.tsx +++ b/src/components/display/Badge.tsx @@ -2,13 +2,20 @@ * Badge Component * * Small status indicators and labels with organic styling. + * Uses paint daube icons for brand consistency. */ -import { Text, View } from 'react-native'; +import type React from 'react'; +import { View } from 'react-native'; +import { CloseIcon, type IconColor, SuccessIcon } from '@/components/icons'; +import { Text } from '@/components/ui'; type BadgeVariant = 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error'; type BadgeSize = 'sm' | 'md' | 'lg'; +/** Badge icon component type */ +type BadgeIconComponent = React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; + interface BadgeProps { /** Badge text content */ children: string; @@ -16,8 +23,12 @@ interface BadgeProps { variant?: BadgeVariant; /** Size variant */ size?: BadgeSize; - /** Optional icon (emoji or text) */ - icon?: string; + /** Optional icon component */ + Icon?: BadgeIconComponent; + /** Icon color override */ + iconColor?: IconColor; + /** Optional text icon (for simple indicators like dots) */ + textIcon?: string; /** Whether to show as a dot only (no text) */ dot?: boolean; } @@ -41,12 +52,18 @@ export function Badge({ children, variant = 'default', size = 'md', - icon, + Icon, + iconColor, + textIcon, dot = false, }: BadgeProps) { const colors = variantStyles[variant]; const sizing = sizeStyles[size]; + // Icon sizes based on badge size + const iconSizes = { sm: 10, md: 12, lg: 14 }; + const iconSize = iconSizes[size]; + if (dot) { return ( - {icon && {icon}} + {Icon && ( + + + + )} + {textIcon && !Icon && {textIcon}} {children} ); @@ -87,20 +109,32 @@ interface StatusBadgeProps { const statusConfig: Record< StatusBadgeProps['status'], - { variant: BadgeVariant; icon: string; label: string } + { + variant: BadgeVariant; + Icon?: BadgeIconComponent; + textIcon?: string; + iconColor: IconColor; + label: string; + } > = { - active: { variant: 'success', icon: 'โ—', label: 'Active' }, - inactive: { variant: 'default', icon: 'โ—‹', label: 'Inactive' }, - pending: { variant: 'warning', icon: 'โ—', label: 'Pending' }, - error: { variant: 'error', icon: 'โœ•', label: 'Error' }, - success: { variant: 'success', icon: 'โœ“', label: 'Success' }, + active: { variant: 'success', textIcon: 'โ—', iconColor: 'teal', label: 'Active' }, + inactive: { variant: 'default', textIcon: 'โ—‹', iconColor: 'warmGray', label: 'Inactive' }, + pending: { variant: 'warning', textIcon: 'โ—', iconColor: 'gold', label: 'Pending' }, + error: { variant: 'error', Icon: CloseIcon, iconColor: 'coral', label: 'Error' }, + success: { variant: 'success', Icon: SuccessIcon, iconColor: 'teal', label: 'Success' }, }; export function StatusBadge({ status, label, size = 'md' }: StatusBadgeProps) { const config = statusConfig[status]; return ( - + {label || config.label} ); diff --git a/src/components/display/EmptyState.tsx b/src/components/display/EmptyState.tsx index 5014522e..98a532c0 100644 --- a/src/components/display/EmptyState.tsx +++ b/src/components/display/EmptyState.tsx @@ -3,14 +3,22 @@ * * Placeholder content for empty lists and screens. * Provides visual feedback and optional call-to-action. + * Uses paint daube icons for brand consistency. */ import type { ReactNode } from 'react'; -import { Pressable, Text, View } from 'react-native'; +import { Pressable, View } from 'react-native'; +import { ErrorIcon, type IconColor, InboxIcon, SearchIcon } from '@/components/icons'; +import { Text } from '@/components/ui'; + +/** Icon component type for EmptyState */ +type EmptyStateIcon = React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; interface EmptyStateProps { - /** Large icon or emoji */ - icon?: string; + /** Paint daube icon component */ + Icon?: EmptyStateIcon; + /** Icon color */ + iconColor?: IconColor; /** Title text */ title: string; /** Description text */ @@ -32,13 +40,14 @@ interface EmptyStateProps { } const sizeStyles = { - sm: { icon: 'text-4xl', title: 'text-base', desc: 'text-sm', padding: 'p-4' }, - md: { icon: 'text-5xl', title: 'text-lg', desc: 'text-sm', padding: 'p-6' }, - lg: { icon: 'text-6xl', title: 'text-xl', desc: 'text-base', padding: 'p-8' }, + sm: { iconSize: 40, title: 'text-base', desc: 'text-sm', padding: 'p-4' }, + md: { iconSize: 56, title: 'text-lg', desc: 'text-sm', padding: 'p-6' }, + lg: { iconSize: 72, title: 'text-xl', desc: 'text-base', padding: 'p-8' }, }; export function EmptyState({ - icon = '๐Ÿ“ญ', + Icon = InboxIcon, + iconColor = 'warmGray', title, description, action, @@ -50,9 +59,13 @@ export function EmptyState({ return ( - {icon} + + + - {title} + + {title} + {description && ( - โš ๏ธ + {/* Error Title */} diff --git a/src/components/feedback/BottomSheet.tsx b/src/components/feedback/BottomSheet.tsx index 13b4bca0..58b48ac6 100644 --- a/src/components/feedback/BottomSheet.tsx +++ b/src/components/feedback/BottomSheet.tsx @@ -18,6 +18,7 @@ import { View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { CloseIcon } from '@/components/icons'; const { height: SCREEN_HEIGHT } = Dimensions.get('window'); @@ -136,7 +137,7 @@ export function BottomSheet({ accessibilityLabel="Close" accessibilityHint="Close the bottom sheet" > - ร— + )} diff --git a/src/components/feedback/Modal.tsx b/src/components/feedback/Modal.tsx index 4badbb04..698d7f55 100644 --- a/src/components/feedback/Modal.tsx +++ b/src/components/feedback/Modal.tsx @@ -8,6 +8,7 @@ import type { ReactNode } from 'react'; import { Pressable, Modal as RNModal, ScrollView, Text, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { CloseIcon } from '@/components/icons'; interface ModalProps { /** Whether the modal is visible */ @@ -87,7 +88,7 @@ export function Modal({ accessibilityLabel="Close" accessibilityHint="Close the modal" > - ร— + )} diff --git a/src/components/feedback/Progress.tsx b/src/components/feedback/Progress.tsx index 697ec7a1..cb8716b2 100644 --- a/src/components/feedback/Progress.tsx +++ b/src/components/feedback/Progress.tsx @@ -2,10 +2,13 @@ * Progress Components * * Progress bars and indicators for tracking completion status. + * Uses paint daube icons for brand consistency. */ import { useEffect, useRef } from 'react'; -import { Animated, Text, View } from 'react-native'; +import { Animated, View } from 'react-native'; +import { SuccessIcon } from '@/components/icons'; +import { Text } from '@/components/ui'; interface ProgressBarProps { /** Progress value between 0 and 100 */ @@ -212,9 +215,11 @@ export function StepsProgress({ totalSteps, currentStep, labels }: StepsProgress borderBottomLeftRadius: 12, }} > - - {isCompleted ? 'โœ“' : stepNum} - + {isCompleted ? ( + + ) : ( + {stepNum} + )} {stepNum < totalSteps && ( = { +/** Toast icon component type */ +type ToastIconComponent = React.FC<{ size?: number; color?: IconColor; turbulence?: number }>; + +interface VariantStyle { + bg: string; + border: string; + Icon: ToastIconComponent; + iconColor: IconColor; +} + +const variantStyles: Record = { success: { bg: 'bg-teal-600/20', border: '#14B8A6', - icon: 'โœ“', - iconColor: 'text-teal-400', + Icon: SuccessIcon, + iconColor: 'teal', }, error: { bg: 'bg-coral-500/20', border: '#FF7059', - icon: 'โœ•', - iconColor: 'text-coral-400', + Icon: CloseIcon, + iconColor: 'coral', }, warning: { bg: 'bg-gold-500/20', border: '#F5D563', - icon: 'โš ', - iconColor: 'text-gold-400', + Icon: WarningIcon, + iconColor: 'gold', }, info: { bg: 'bg-neutral-600/20', border: '#6B7280', - icon: 'โ„น', - iconColor: 'text-neutral-400', + Icon: InfoIcon, + iconColor: 'warmGray', }, }; @@ -143,7 +154,9 @@ export function Toast({ borderLeftColor: styles.border, }} > - {styles.icon} + + + {title && {title}} @@ -157,7 +170,7 @@ export function Toast({ )} - ร— + diff --git a/src/components/form/Checkbox.tsx b/src/components/form/Checkbox.tsx index eb7373f3..50179c32 100644 --- a/src/components/form/Checkbox.tsx +++ b/src/components/form/Checkbox.tsx @@ -3,9 +3,12 @@ * * A checkbox input with label support and organic styling. * Follows accessibility best practices. + * Uses paint daube icons for brand consistency. */ -import { Pressable, Text, View } from 'react-native'; +import { Pressable, View } from 'react-native'; +import { SuccessIcon } from '@/components/icons'; +import { Text } from '@/components/ui'; interface CheckboxProps { /** Whether the checkbox is checked */ @@ -59,14 +62,7 @@ export function Checkbox({ borderBottomLeftRadius: 6, }} > - {checked && ( - - โœ“ - - )} + {checked && } {(label || description) && ( diff --git a/src/components/form/Select.tsx b/src/components/form/Select.tsx index 25c0af90..1ffe9da5 100644 --- a/src/components/form/Select.tsx +++ b/src/components/form/Select.tsx @@ -3,10 +3,13 @@ * * A dropdown select input with organic styling. * Displays options in a modal on mobile. + * Uses paint daube icons for brand consistency. */ import { useState } from 'react'; -import { FlatList, Modal, Pressable, Text, View } from 'react-native'; +import { FlatList, Modal, Pressable, View } from 'react-native'; +import { ChevronDownIcon, SuccessIcon } from '@/components/icons'; +import { Text } from '@/components/ui'; interface SelectOption { value: string; @@ -77,7 +80,9 @@ export function Select({ > {selectedOption?.label || placeholder} - โ–ผ + + + {error && {error}} @@ -124,7 +129,9 @@ export function Select({ > {item.label} - {item.value === value && โœ“} + {item.value === value && ( + + )} )} diff --git a/src/components/icons/PaintDaubeIcon.tsx b/src/components/icons/PaintDaubeIcon.tsx new file mode 100644 index 00000000..e0b9aba5 --- /dev/null +++ b/src/components/icons/PaintDaubeIcon.tsx @@ -0,0 +1,1268 @@ +/** + * PaintDaubeIcon - Procedural Organic SVG Icon System + * + * ThumbCode's brand identity uses organic "paint daube" shapes instead of + * traditional geometric icons. This component generates procedural SVG + * icons with feTurbulence filters for authentic paint texture. + * + * Brand Guidelines: + * - Organic, imperfect shapes (NOT circles/squares) + * - feTurbulence filters for paint texture + * - Brand colors only (coral, teal, gold) + * - Outline style with rounded caps + * - Asymmetric border paths + * + * @see docs/brand/BRAND-GUIDELINES.md + */ + +import { View } from 'react-native'; +import Svg, { Defs, FeDisplacementMap, FeTurbulence, Filter, G, Path } from 'react-native-svg'; + +// Brand color hex values aligned with CLAUDE.md P3 "Warm Technical" palette +export const BRAND_COLORS = { + coral: '#FF7059', // Primary - Thumb Coral + coralDark: '#E85A4F', // Light mode variant + teal: '#0D9488', // Secondary - Digital Teal + tealDark: '#0F766E', // Light mode variant + gold: '#F5D563', // Accent - Soft Gold + goldDark: '#D4A84B', // Light mode variant + charcoal: '#151820', // Base Dark - Charcoal Navy + warmGray: '#696259', +} as const; + +export type IconColor = keyof typeof BRAND_COLORS; + +export type IconVariant = + | 'agent' // Robot/AI + | 'mobile' // Phone/device + | 'security' // Lock/shield + | 'lightning' // Speed/power + | 'success' // Checkmark + | 'celebrate' // Party/celebration + | 'git' // Version control + | 'settings' // Gear/cog + | 'chat' // Message bubble + | 'code' // Brackets + | 'folder' // Directory + | 'folderOpen' // Open directory + | 'star' // Favorite + | 'key' // API key + | 'github' // GitHub logo + | 'thumb' // ThumbCode logo - thumbs up + | 'home' // Dashboard/home + | 'tasks' // Clipboard/tasks + | 'search' // Magnifying glass + | 'bell' // Notifications + | 'link' // Chain link + | 'brain' // AI/thinking + | 'palette' // Appearance/theme + | 'vibrate' // Haptic feedback + | 'keyboard' // Editor/input + | 'branch' // Git branch + | 'book' // Documentation + | 'support' // Help/support + | 'legal' // Terms/scales + | 'info' // Information + | 'user' // User/profile + | 'edit' // Pencil/edit + | 'review' // Review/magnifier + | 'inbox' // Empty inbox + | 'error' // Error/warning + | 'test' // Test/beaker + | 'file' // Generic file + | 'fileCode' // Code file (ts/js) + | 'fileData' // Data file (json) + | 'fileDoc' // Document (md) + | 'fileStyle' // Style file (css) + | 'fileWeb' // Web file (html) + | 'fileMedia' // Media file (image) + | 'fileConfig' // Config file (env, lock) + | 'close' // X mark / close + | 'lightbulb' // Tip/idea + | 'warning' // Warning symbol + | 'chevronDown'; // Dropdown arrow + +interface PaintDaubeIconProps { + variant: IconVariant; + color?: IconColor; + size?: number; + /** Intensity of the turbulence effect (0-1) */ + turbulence?: number; + /** Unique seed for procedural variation */ + seed?: number; +} + +/** + * Generates an organic path with slight imperfections + * Uses seed for reproducible randomness + */ +function generateOrganicOffset(seed: number, index: number, maxOffset: number = 1.5): number { + // Simple seeded pseudo-random + const x = Math.sin(seed * 9999 + index * 7919) * 10000; + return (x - Math.floor(x) - 0.5) * maxOffset * 2; +} + +/** + * Paint Daube SVG Filter Definition + * Creates the organic paint texture effect + */ +function PaintDaubeFilter({ id, turbulence }: { id: string; turbulence: number }) { + return ( + + + + + + + ); +} + +/** + * Icon path definitions - organic outlines with rounded joins + * Each path is designed to be imperfect and hand-drawn feeling + */ +const ICON_PATHS: Record string> = { + agent: (seed) => { + // Robot head with antenna - organic shape + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${4 + o(1)} + L ${12 + o(2)} ${7 + o(3)} + M ${6 + o(4)} ${9 + o(5)} + C ${4 + o(6)} ${9 + o(7)}, ${3 + o(8)} ${11 + o(9)}, ${3 + o(10)} ${13 + o(11)} + L ${3 + o(12)} ${18 + o(13)} + C ${3 + o(14)} ${20 + o(15)}, ${5 + o(16)} ${21 + o(17)}, ${7 + o(18)} ${21 + o(19)} + L ${17 + o(20)} ${21 + o(21)} + C ${19 + o(22)} ${21 + o(23)}, ${21 + o(24)} ${20 + o(25)}, ${21 + o(26)} ${18 + o(27)} + L ${21 + o(28)} ${13 + o(29)} + C ${21 + o(30)} ${11 + o(31)}, ${20 + o(32)} ${9 + o(33)}, ${18 + o(34)} ${9 + o(35)} + Z + M ${8 + o(36)} ${13 + o(37)} + C ${8.5 + o(38)} ${12.5 + o(39)}, ${9.5 + o(40)} ${12.5 + o(41)}, ${10 + o(42)} ${13 + o(43)} + M ${14 + o(44)} ${13 + o(45)} + C ${14.5 + o(46)} ${12.5 + o(47)}, ${15.5 + o(48)} ${12.5 + o(49)}, ${16 + o(50)} ${13 + o(51)} + M ${9 + o(52)} ${17 + o(53)} + L ${15 + o(54)} ${17 + o(55)} + `; + }, + + mobile: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${7 + o(0)} ${2 + o(1)} + C ${5.5 + o(2)} ${2 + o(3)}, ${5 + o(4)} ${3.5 + o(5)}, ${5 + o(6)} ${5 + o(7)} + L ${5 + o(8)} ${19 + o(9)} + C ${5 + o(10)} ${20.5 + o(11)}, ${5.5 + o(12)} ${22 + o(13)}, ${7 + o(14)} ${22 + o(15)} + L ${17 + o(16)} ${22 + o(17)} + C ${18.5 + o(18)} ${22 + o(19)}, ${19 + o(20)} ${20.5 + o(21)}, ${19 + o(22)} ${19 + o(23)} + L ${19 + o(24)} ${5 + o(25)} + C ${19 + o(26)} ${3.5 + o(27)}, ${18.5 + o(28)} ${2 + o(29)}, ${17 + o(30)} ${2 + o(31)} + Z + M ${11 + o(32)} ${18 + o(33)} + L ${13 + o(34)} ${18 + o(35)} + `; + }, + + security: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${2 + o(1)} + L ${4 + o(2)} ${5 + o(3)} + C ${4 + o(4)} ${5 + o(5)}, ${4 + o(6)} ${12 + o(7)}, ${12 + o(8)} ${22 + o(9)} + C ${20 + o(10)} ${12 + o(11)}, ${20 + o(12)} ${5 + o(13)}, ${20 + o(14)} ${5 + o(15)} + Z + M ${12 + o(16)} ${8 + o(17)} + L ${12 + o(18)} ${12 + o(19)} + M ${12 + o(20)} ${15 + o(21)} + L ${12 + o(22)} ${15.5 + o(23)} + `; + }, + + lightning: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${13 + o(0)} ${2 + o(1)} + L ${4 + o(2)} ${14 + o(3)} + L ${11 + o(4)} ${14 + o(5)} + L ${11 + o(6)} ${22 + o(7)} + L ${20 + o(8)} ${10 + o(9)} + L ${13 + o(10)} ${10 + o(11)} + Z + `; + }, + + success: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${5 + o(0)} ${12 + o(1)} + L ${10 + o(2)} ${17 + o(3)} + L ${20 + o(4)} ${7 + o(5)} + `; + }, + + celebrate: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${4 + o(0)} ${21 + o(1)} + L ${10 + o(2)} ${3 + o(3)} + L ${14 + o(4)} ${8 + o(5)} + L ${10 + o(6)} ${3 + o(7)} + L ${17 + o(8)} ${6 + o(9)} + M ${15 + o(10)} ${12 + o(11)} + L ${15.5 + o(12)} ${12.5 + o(13)} + M ${19 + o(14)} ${9 + o(15)} + L ${19.5 + o(16)} ${9.5 + o(17)} + M ${18 + o(18)} ${15 + o(19)} + L ${18.5 + o(20)} ${15.5 + o(21)} + M ${8 + o(22)} ${7 + o(23)} + L ${8.5 + o(24)} ${7.5 + o(25)} + `; + }, + + git: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${3 + o(1)} + L ${12 + o(2)} ${8 + o(3)} + M ${12 + o(4)} ${16 + o(5)} + L ${12 + o(6)} ${21 + o(7)} + M ${5 + o(8)} ${12 + o(9)} + L ${12 + o(10)} ${12 + o(11)} + M ${12 + o(12)} ${8 + o(13)} + C ${15 + o(14)} ${8 + o(15)}, ${17 + o(16)} ${10 + o(17)}, ${17 + o(18)} ${12 + o(19)} + C ${17 + o(20)} ${14 + o(21)}, ${15 + o(22)} ${16 + o(23)}, ${12 + o(24)} ${16 + o(25)} + C ${9 + o(26)} ${16 + o(27)}, ${7 + o(28)} ${14 + o(29)}, ${7 + o(30)} ${12 + o(31)} + C ${7 + o(32)} ${10 + o(33)}, ${9 + o(34)} ${8 + o(35)}, ${12 + o(36)} ${8 + o(37)} + `; + }, + + settings: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${15 + o(1)} + C ${13.7 + o(2)} ${15 + o(3)}, ${15 + o(4)} ${13.7 + o(5)}, ${15 + o(6)} ${12 + o(7)} + C ${15 + o(8)} ${10.3 + o(9)}, ${13.7 + o(10)} ${9 + o(11)}, ${12 + o(12)} ${9 + o(13)} + C ${10.3 + o(14)} ${9 + o(15)}, ${9 + o(16)} ${10.3 + o(17)}, ${9 + o(18)} ${12 + o(19)} + C ${9 + o(20)} ${13.7 + o(21)}, ${10.3 + o(22)} ${15 + o(23)}, ${12 + o(24)} ${15 + o(25)} + M ${12 + o(26)} ${3 + o(27)} + L ${12 + o(28)} ${6 + o(29)} + M ${12 + o(30)} ${18 + o(31)} + L ${12 + o(32)} ${21 + o(33)} + M ${4.2 + o(34)} ${7.5 + o(35)} + L ${6.8 + o(36)} ${9.2 + o(37)} + M ${17.2 + o(38)} ${14.8 + o(39)} + L ${19.8 + o(40)} ${16.5 + o(41)} + M ${3 + o(42)} ${12 + o(43)} + L ${6 + o(44)} ${12 + o(45)} + M ${18 + o(46)} ${12 + o(47)} + L ${21 + o(48)} ${12 + o(49)} + M ${4.2 + o(50)} ${16.5 + o(51)} + L ${6.8 + o(52)} ${14.8 + o(53)} + M ${17.2 + o(54)} ${9.2 + o(55)} + L ${19.8 + o(56)} ${7.5 + o(57)} + `; + }, + + chat: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${4 + o(0)} ${4 + o(1)} + C ${3 + o(2)} ${4 + o(3)}, ${2 + o(4)} ${5 + o(5)}, ${2 + o(6)} ${6 + o(7)} + L ${2 + o(8)} ${14 + o(9)} + C ${2 + o(10)} ${15 + o(11)}, ${3 + o(12)} ${16 + o(13)}, ${4 + o(14)} ${16 + o(15)} + L ${6 + o(16)} ${16 + o(17)} + L ${6 + o(18)} ${20 + o(19)} + L ${10 + o(20)} ${16 + o(21)} + L ${20 + o(22)} ${16 + o(23)} + C ${21 + o(24)} ${16 + o(25)}, ${22 + o(26)} ${15 + o(27)}, ${22 + o(28)} ${14 + o(29)} + L ${22 + o(30)} ${6 + o(31)} + C ${22 + o(32)} ${5 + o(33)}, ${21 + o(34)} ${4 + o(35)}, ${20 + o(36)} ${4 + o(37)} + Z + `; + }, + + code: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${8 + o(0)} ${6 + o(1)} + L ${3 + o(2)} ${12 + o(3)} + L ${8 + o(4)} ${18 + o(5)} + M ${16 + o(6)} ${6 + o(7)} + L ${21 + o(8)} ${12 + o(9)} + L ${16 + o(10)} ${18 + o(11)} + `; + }, + + folder: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${4 + o(0)} ${4 + o(1)} + C ${3 + o(2)} ${4 + o(3)}, ${2 + o(4)} ${5 + o(5)}, ${2 + o(6)} ${6 + o(7)} + L ${2 + o(8)} ${18 + o(9)} + C ${2 + o(10)} ${19 + o(11)}, ${3 + o(12)} ${20 + o(13)}, ${4 + o(14)} ${20 + o(15)} + L ${20 + o(16)} ${20 + o(17)} + C ${21 + o(18)} ${20 + o(19)}, ${22 + o(20)} ${19 + o(21)}, ${22 + o(22)} ${18 + o(23)} + L ${22 + o(24)} ${10 + o(25)} + C ${22 + o(26)} ${9 + o(27)}, ${21 + o(28)} ${8 + o(29)}, ${20 + o(30)} ${8 + o(31)} + L ${12 + o(32)} ${8 + o(33)} + L ${10 + o(34)} ${4 + o(35)} + Z + `; + }, + + star: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${2 + o(1)} + L ${14.5 + o(2)} ${8.5 + o(3)} + L ${22 + o(4)} ${9 + o(5)} + L ${16.5 + o(6)} ${14 + o(7)} + L ${18 + o(8)} ${22 + o(9)} + L ${12 + o(10)} ${18 + o(11)} + L ${6 + o(12)} ${22 + o(13)} + L ${7.5 + o(14)} ${14 + o(15)} + L ${2 + o(16)} ${9 + o(17)} + L ${9.5 + o(18)} ${8.5 + o(19)} + Z + `; + }, + + key: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${15 + o(0)} ${7 + o(1)} + C ${17.2 + o(2)} ${7 + o(3)}, ${19 + o(4)} ${8.8 + o(5)}, ${19 + o(6)} ${11 + o(7)} + C ${19 + o(8)} ${13.2 + o(9)}, ${17.2 + o(10)} ${15 + o(11)}, ${15 + o(12)} ${15 + o(13)} + C ${13.5 + o(14)} ${15 + o(15)}, ${12.2 + o(16)} ${14.4 + o(17)}, ${11.3 + o(18)} ${13.4 + o(19)} + L ${3 + o(20)} ${22 + o(21)} + L ${3 + o(22)} ${17 + o(23)} + L ${6 + o(24)} ${17 + o(25)} + L ${6 + o(26)} ${14 + o(27)} + L ${9 + o(28)} ${14 + o(29)} + L ${9.6 + o(30)} ${11.3 + o(31)} + C ${9 + o(32)} ${10.2 + o(33)}, ${9 + o(34)} ${8.5 + o(35)}, ${9.5 + o(36)} ${7.5 + o(37)} + C ${10.5 + o(38)} ${6.5 + o(39)}, ${12.8 + o(40)} ${7 + o(41)}, ${15 + o(42)} ${7 + o(43)} + M ${15 + o(44)} ${10 + o(45)} + L ${15.5 + o(46)} ${10.5 + o(47)} + `; + }, + + github: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i, 0.8); + return ` + M ${12 + o(0)} ${2 + o(1)} + C ${6.5 + o(2)} ${2 + o(3)}, ${2 + o(4)} ${6.5 + o(5)}, ${2 + o(6)} ${12 + o(7)} + C ${2 + o(8)} ${16 + o(9)}, ${4.5 + o(10)} ${19.5 + o(11)}, ${8 + o(12)} ${21 + o(13)} + C ${8.5 + o(14)} ${21.2 + o(15)}, ${9 + o(16)} ${20.5 + o(17)}, ${9 + o(18)} ${20 + o(19)} + L ${9 + o(20)} ${17 + o(21)} + C ${6.5 + o(22)} ${17.5 + o(23)}, ${5 + o(24)} ${15 + o(25)}, ${5 + o(26)} ${13 + o(27)} + L ${6 + o(28)} ${13 + o(29)} + C ${5 + o(30)} ${11 + o(31)}, ${6 + o(32)} ${9 + o(33)}, ${8 + o(34)} ${8 + o(35)} + C ${8 + o(36)} ${6 + o(37)}, ${9 + o(38)} ${4 + o(39)}, ${12 + o(40)} ${4 + o(41)} + C ${15 + o(42)} ${4 + o(43)}, ${16 + o(44)} ${6 + o(45)}, ${16 + o(46)} ${8 + o(47)} + C ${18 + o(48)} ${9 + o(49)}, ${19 + o(50)} ${11 + o(51)}, ${18 + o(52)} ${13 + o(53)} + L ${19 + o(54)} ${13 + o(55)} + C ${19 + o(56)} ${15 + o(57)}, ${17.5 + o(58)} ${17.5 + o(59)}, ${15 + o(60)} ${17 + o(61)} + L ${15 + o(62)} ${20 + o(63)} + C ${15 + o(64)} ${20.5 + o(65)}, ${15.5 + o(66)} ${21.2 + o(67)}, ${16 + o(68)} ${21 + o(69)} + C ${19.5 + o(70)} ${19.5 + o(71)}, ${22 + o(72)} ${16 + o(73)}, ${22 + o(74)} ${12 + o(75)} + C ${22 + o(76)} ${6.5 + o(77)}, ${17.5 + o(78)} ${2 + o(79)}, ${12 + o(80)} ${2 + o(81)} + `; + }, + + thumb: (seed) => { + // Thumbs up - organic hand shape + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${7 + o(0)} ${22 + o(1)} + L ${7 + o(2)} ${13 + o(3)} + C ${7 + o(4)} ${12 + o(5)}, ${8 + o(6)} ${11 + o(7)}, ${9 + o(8)} ${10 + o(9)} + L ${9 + o(10)} ${5 + o(11)} + C ${9 + o(12)} ${3 + o(13)}, ${11 + o(14)} ${2 + o(15)}, ${12 + o(16)} ${2 + o(17)} + C ${13 + o(18)} ${2 + o(19)}, ${14 + o(20)} ${3 + o(21)}, ${14 + o(22)} ${4 + o(23)} + L ${14 + o(24)} ${9 + o(25)} + L ${19 + o(26)} ${9 + o(27)} + C ${20.5 + o(28)} ${9 + o(29)}, ${22 + o(30)} ${10.5 + o(31)}, ${22 + o(32)} ${12 + o(33)} + L ${22 + o(34)} ${17 + o(35)} + C ${22 + o(36)} ${19.5 + o(37)}, ${20 + o(38)} ${22 + o(39)}, ${17 + o(40)} ${22 + o(41)} + Z + M ${2 + o(42)} ${13 + o(43)} + L ${7 + o(44)} ${13 + o(45)} + L ${7 + o(46)} ${22 + o(47)} + L ${2 + o(48)} ${22 + o(49)} + C ${2 + o(50)} ${22 + o(51)}, ${2 + o(52)} ${13 + o(53)}, ${2 + o(54)} ${13 + o(55)} + `; + }, + + home: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${3 + o(0)} ${10 + o(1)} + L ${12 + o(2)} ${3 + o(3)} + L ${21 + o(4)} ${10 + o(5)} + M ${5 + o(6)} ${10 + o(7)} + L ${5 + o(8)} ${19 + o(9)} + C ${5 + o(10)} ${20 + o(11)}, ${6 + o(12)} ${21 + o(13)}, ${7 + o(14)} ${21 + o(15)} + L ${17 + o(16)} ${21 + o(17)} + C ${18 + o(18)} ${21 + o(19)}, ${19 + o(20)} ${20 + o(21)}, ${19 + o(22)} ${19 + o(23)} + L ${19 + o(24)} ${10 + o(25)} + `; + }, + + tasks: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${9 + o(0)} ${2 + o(1)} + L ${15 + o(2)} ${2 + o(3)} + M ${6 + o(4)} ${4 + o(5)} + C ${5 + o(6)} ${4 + o(7)}, ${4 + o(8)} ${5 + o(9)}, ${4 + o(10)} ${6 + o(11)} + L ${4 + o(12)} ${20 + o(13)} + C ${4 + o(14)} ${21 + o(15)}, ${5 + o(16)} ${22 + o(17)}, ${6 + o(18)} ${22 + o(19)} + L ${18 + o(20)} ${22 + o(21)} + C ${19 + o(22)} ${22 + o(23)}, ${20 + o(24)} ${21 + o(25)}, ${20 + o(26)} ${20 + o(27)} + L ${20 + o(28)} ${6 + o(29)} + C ${20 + o(30)} ${5 + o(31)}, ${19 + o(32)} ${4 + o(33)}, ${18 + o(34)} ${4 + o(35)} + L ${16 + o(36)} ${4 + o(37)} + M ${8 + o(38)} ${4 + o(39)} + L ${8 + o(40)} ${4 + o(41)} + M ${8 + o(42)} ${10 + o(43)} + L ${16 + o(44)} ${10 + o(45)} + M ${8 + o(46)} ${14 + o(47)} + L ${16 + o(48)} ${14 + o(49)} + M ${8 + o(50)} ${18 + o(51)} + L ${12 + o(52)} ${18 + o(53)} + `; + }, + + search: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${10 + o(0)} ${3 + o(1)} + C ${14 + o(2)} ${3 + o(3)}, ${17 + o(4)} ${6 + o(5)}, ${17 + o(6)} ${10 + o(7)} + C ${17 + o(8)} ${14 + o(9)}, ${14 + o(10)} ${17 + o(11)}, ${10 + o(12)} ${17 + o(13)} + C ${6 + o(14)} ${17 + o(15)}, ${3 + o(16)} ${14 + o(17)}, ${3 + o(18)} ${10 + o(19)} + C ${3 + o(20)} ${6 + o(21)}, ${6 + o(22)} ${3 + o(23)}, ${10 + o(24)} ${3 + o(25)} + M ${15 + o(26)} ${15 + o(27)} + L ${21 + o(28)} ${21 + o(29)} + `; + }, + + bell: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${2 + o(1)} + C ${12.5 + o(2)} ${2 + o(3)}, ${13 + o(4)} ${2.5 + o(5)}, ${13 + o(6)} ${3 + o(7)} + L ${13 + o(8)} ${4 + o(9)} + C ${16 + o(10)} ${4.5 + o(11)}, ${18 + o(12)} ${7 + o(13)}, ${18 + o(14)} ${10 + o(15)} + L ${18 + o(16)} ${16 + o(17)} + L ${20 + o(18)} ${18 + o(19)} + L ${4 + o(20)} ${18 + o(21)} + L ${6 + o(22)} ${16 + o(23)} + L ${6 + o(24)} ${10 + o(25)} + C ${6 + o(26)} ${7 + o(27)}, ${8 + o(28)} ${4.5 + o(29)}, ${11 + o(30)} ${4 + o(31)} + L ${11 + o(32)} ${3 + o(33)} + C ${11 + o(34)} ${2.5 + o(35)}, ${11.5 + o(36)} ${2 + o(37)}, ${12 + o(38)} ${2 + o(39)} + M ${10 + o(40)} ${21 + o(41)} + C ${10 + o(42)} ${22 + o(43)}, ${11 + o(44)} ${22 + o(45)}, ${12 + o(46)} ${22 + o(47)} + C ${13 + o(48)} ${22 + o(49)}, ${14 + o(50)} ${22 + o(51)}, ${14 + o(52)} ${21 + o(53)} + `; + }, + + link: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${10 + o(0)} ${14 + o(1)} + C ${8 + o(2)} ${14 + o(3)}, ${6 + o(4)} ${12 + o(5)}, ${6 + o(6)} ${10 + o(7)} + C ${6 + o(8)} ${8 + o(9)}, ${8 + o(10)} ${6 + o(11)}, ${10 + o(12)} ${6 + o(13)} + L ${14 + o(14)} ${6 + o(15)} + C ${16 + o(16)} ${6 + o(17)}, ${18 + o(18)} ${8 + o(19)}, ${18 + o(20)} ${10 + o(21)} + M ${14 + o(22)} ${10 + o(23)} + C ${16 + o(24)} ${10 + o(25)}, ${18 + o(26)} ${12 + o(27)}, ${18 + o(28)} ${14 + o(29)} + C ${18 + o(30)} ${16 + o(31)}, ${16 + o(32)} ${18 + o(33)}, ${14 + o(34)} ${18 + o(35)} + L ${10 + o(36)} ${18 + o(37)} + C ${8 + o(38)} ${18 + o(39)}, ${6 + o(40)} ${16 + o(41)}, ${6 + o(42)} ${14 + o(43)} + `; + }, + + brain: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${3 + o(1)} + C ${9 + o(2)} ${3 + o(3)}, ${7 + o(4)} ${5 + o(5)}, ${7 + o(6)} ${8 + o(7)} + C ${5 + o(8)} ${8 + o(9)}, ${4 + o(10)} ${10 + o(11)}, ${4 + o(12)} ${12 + o(13)} + C ${4 + o(14)} ${14 + o(15)}, ${5 + o(16)} ${16 + o(17)}, ${7 + o(18)} ${17 + o(19)} + C ${7 + o(20)} ${19 + o(21)}, ${9 + o(22)} ${21 + o(23)}, ${12 + o(24)} ${21 + o(25)} + C ${15 + o(26)} ${21 + o(27)}, ${17 + o(28)} ${19 + o(29)}, ${17 + o(30)} ${17 + o(31)} + C ${19 + o(32)} ${16 + o(33)}, ${20 + o(34)} ${14 + o(35)}, ${20 + o(36)} ${12 + o(37)} + C ${20 + o(38)} ${10 + o(39)}, ${19 + o(40)} ${8 + o(41)}, ${17 + o(42)} ${8 + o(43)} + C ${17 + o(44)} ${5 + o(45)}, ${15 + o(46)} ${3 + o(47)}, ${12 + o(48)} ${3 + o(49)} + M ${12 + o(50)} ${8 + o(51)} + L ${12 + o(52)} ${12 + o(53)} + M ${9 + o(54)} ${10 + o(55)} + L ${12 + o(56)} ${12 + o(57)} + L ${15 + o(58)} ${10 + o(59)} + M ${12 + o(60)} ${12 + o(61)} + L ${12 + o(62)} ${16 + o(63)} + `; + }, + + palette: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${2 + o(1)} + C ${6 + o(2)} ${2 + o(3)}, ${2 + o(4)} ${7 + o(5)}, ${2 + o(6)} ${12 + o(7)} + C ${2 + o(8)} ${17 + o(9)}, ${6 + o(10)} ${22 + o(11)}, ${12 + o(12)} ${22 + o(13)} + C ${12 + o(14)} ${22 + o(15)}, ${14 + o(16)} ${20 + o(17)}, ${14 + o(18)} ${18 + o(19)} + C ${14 + o(20)} ${16 + o(21)}, ${16 + o(22)} ${14 + o(23)}, ${18 + o(24)} ${14 + o(25)} + C ${20 + o(26)} ${14 + o(27)}, ${22 + o(28)} ${12 + o(29)}, ${22 + o(30)} ${10 + o(31)} + C ${22 + o(32)} ${6 + o(33)}, ${18 + o(34)} ${2 + o(35)}, ${12 + o(36)} ${2 + o(37)} + M ${7 + o(38)} ${12 + o(39)} + L ${7.5 + o(40)} ${12.5 + o(41)} + M ${12 + o(42)} ${7 + o(43)} + L ${12.5 + o(44)} ${7.5 + o(45)} + M ${17 + o(46)} ${9 + o(47)} + L ${17.5 + o(48)} ${9.5 + o(49)} + `; + }, + + vibrate: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${2 + o(0)} ${8 + o(1)} + L ${2 + o(2)} ${16 + o(3)} + M ${6 + o(4)} ${6 + o(5)} + L ${6 + o(6)} ${18 + o(7)} + M ${9 + o(8)} ${4 + o(9)} + L ${9 + o(10)} ${20 + o(11)} + L ${15 + o(12)} ${20 + o(13)} + L ${15 + o(14)} ${4 + o(15)} + L ${9 + o(16)} ${4 + o(17)} + M ${18 + o(18)} ${6 + o(19)} + L ${18 + o(20)} ${18 + o(21)} + M ${22 + o(22)} ${8 + o(23)} + L ${22 + o(24)} ${16 + o(25)} + `; + }, + + keyboard: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${2 + o(0)} ${6 + o(1)} + C ${2 + o(2)} ${5 + o(3)}, ${3 + o(4)} ${4 + o(5)}, ${4 + o(6)} ${4 + o(7)} + L ${20 + o(8)} ${4 + o(9)} + C ${21 + o(10)} ${4 + o(11)}, ${22 + o(12)} ${5 + o(13)}, ${22 + o(14)} ${6 + o(15)} + L ${22 + o(16)} ${18 + o(17)} + C ${22 + o(18)} ${19 + o(19)}, ${21 + o(20)} ${20 + o(21)}, ${20 + o(22)} ${20 + o(23)} + L ${4 + o(24)} ${20 + o(25)} + C ${3 + o(26)} ${20 + o(27)}, ${2 + o(28)} ${19 + o(29)}, ${2 + o(30)} ${18 + o(31)} + Z + M ${6 + o(32)} ${8 + o(33)} + L ${6.5 + o(34)} ${8.5 + o(35)} + M ${10 + o(36)} ${8 + o(37)} + L ${10.5 + o(38)} ${8.5 + o(39)} + M ${14 + o(40)} ${8 + o(41)} + L ${14.5 + o(42)} ${8.5 + o(43)} + M ${18 + o(44)} ${8 + o(45)} + L ${18.5 + o(46)} ${8.5 + o(47)} + M ${7 + o(48)} ${16 + o(49)} + L ${17 + o(50)} ${16 + o(51)} + `; + }, + + branch: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${3 + o(1)} + L ${6 + o(2)} ${15 + o(3)} + C ${6 + o(4)} ${17 + o(5)}, ${8 + o(6)} ${19 + o(7)}, ${10 + o(8)} ${19 + o(9)} + L ${18 + o(10)} ${19 + o(11)} + M ${18 + o(12)} ${3 + o(13)} + L ${18 + o(14)} ${7 + o(15)} + C ${18 + o(16)} ${9 + o(17)}, ${16 + o(18)} ${11 + o(19)}, ${14 + o(20)} ${11 + o(21)} + L ${6 + o(22)} ${11 + o(23)} + M ${6 + o(24)} ${6 + o(25)} + C ${7.5 + o(26)} ${6 + o(27)}, ${8 + o(28)} ${4.5 + o(29)}, ${8 + o(30)} ${3 + o(31)} + C ${8 + o(32)} ${1.5 + o(33)}, ${7.5 + o(34)} ${1 + o(35)}, ${6 + o(36)} ${1 + o(37)} + C ${4.5 + o(38)} ${1 + o(39)}, ${4 + o(40)} ${1.5 + o(41)}, ${4 + o(42)} ${3 + o(43)} + C ${4 + o(44)} ${4.5 + o(45)}, ${4.5 + o(46)} ${6 + o(47)}, ${6 + o(48)} ${6 + o(49)} + `; + }, + + book: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${4 + o(0)} ${4 + o(1)} + C ${4 + o(2)} ${3 + o(3)}, ${5 + o(4)} ${2 + o(5)}, ${6 + o(6)} ${2 + o(7)} + L ${12 + o(8)} ${2 + o(9)} + M ${12 + o(10)} ${2 + o(11)} + L ${18 + o(12)} ${2 + o(13)} + C ${19 + o(14)} ${2 + o(15)}, ${20 + o(16)} ${3 + o(17)}, ${20 + o(18)} ${4 + o(19)} + L ${20 + o(20)} ${18 + o(21)} + C ${20 + o(22)} ${19 + o(23)}, ${19 + o(24)} ${20 + o(25)}, ${18 + o(26)} ${20 + o(27)} + L ${12 + o(28)} ${20 + o(29)} + M ${4 + o(30)} ${4 + o(31)} + L ${4 + o(32)} ${18 + o(33)} + C ${4 + o(34)} ${19 + o(35)}, ${5 + o(36)} ${20 + o(37)}, ${6 + o(38)} ${20 + o(39)} + L ${12 + o(40)} ${20 + o(41)} + M ${12 + o(42)} ${2 + o(43)} + L ${12 + o(44)} ${20 + o(45)} + `; + }, + + support: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${2 + o(1)} + C ${6 + o(2)} ${2 + o(3)}, ${2 + o(4)} ${6 + o(5)}, ${2 + o(6)} ${12 + o(7)} + C ${2 + o(8)} ${18 + o(9)}, ${6 + o(10)} ${22 + o(11)}, ${12 + o(12)} ${22 + o(13)} + C ${18 + o(14)} ${22 + o(15)}, ${22 + o(16)} ${18 + o(17)}, ${22 + o(18)} ${12 + o(19)} + C ${22 + o(20)} ${6 + o(21)}, ${18 + o(22)} ${2 + o(23)}, ${12 + o(24)} ${2 + o(25)} + M ${12 + o(26)} ${8 + o(27)} + C ${10 + o(28)} ${8 + o(29)}, ${9 + o(30)} ${9.5 + o(31)}, ${9 + o(32)} ${11 + o(33)} + C ${9 + o(34)} ${12.5 + o(35)}, ${10 + o(36)} ${14 + o(37)}, ${12 + o(38)} ${14 + o(39)} + L ${12 + o(40)} ${16 + o(41)} + M ${12 + o(42)} ${19 + o(43)} + L ${12.5 + o(44)} ${19.5 + o(45)} + `; + }, + + legal: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${3 + o(1)} + L ${12 + o(2)} ${21 + o(3)} + M ${4 + o(4)} ${8 + o(5)} + L ${20 + o(6)} ${8 + o(7)} + M ${5 + o(8)} ${8 + o(9)} + L ${3 + o(10)} ${14 + o(11)} + C ${3 + o(12)} ${16 + o(13)}, ${5 + o(14)} ${16 + o(15)}, ${7 + o(16)} ${14 + o(17)} + L ${5 + o(18)} ${8 + o(19)} + M ${19 + o(20)} ${8 + o(21)} + L ${21 + o(22)} ${14 + o(23)} + C ${21 + o(24)} ${16 + o(25)}, ${19 + o(26)} ${16 + o(27)}, ${17 + o(28)} ${14 + o(29)} + L ${19 + o(30)} ${8 + o(31)} + `; + }, + + info: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${2 + o(1)} + C ${6.5 + o(2)} ${2 + o(3)}, ${2 + o(4)} ${6.5 + o(5)}, ${2 + o(6)} ${12 + o(7)} + C ${2 + o(8)} ${17.5 + o(9)}, ${6.5 + o(10)} ${22 + o(11)}, ${12 + o(12)} ${22 + o(13)} + C ${17.5 + o(14)} ${22 + o(15)}, ${22 + o(16)} ${17.5 + o(17)}, ${22 + o(18)} ${12 + o(19)} + C ${22 + o(20)} ${6.5 + o(21)}, ${17.5 + o(22)} ${2 + o(23)}, ${12 + o(24)} ${2 + o(25)} + M ${12 + o(26)} ${8 + o(27)} + L ${12.5 + o(28)} ${8.5 + o(29)} + M ${12 + o(30)} ${12 + o(31)} + L ${12 + o(32)} ${17 + o(33)} + `; + }, + + user: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${4 + o(1)} + C ${14.5 + o(2)} ${4 + o(3)}, ${16 + o(4)} ${5.5 + o(5)}, ${16 + o(6)} ${8 + o(7)} + C ${16 + o(8)} ${10.5 + o(9)}, ${14.5 + o(10)} ${12 + o(11)}, ${12 + o(12)} ${12 + o(13)} + C ${9.5 + o(14)} ${12 + o(15)}, ${8 + o(16)} ${10.5 + o(17)}, ${8 + o(18)} ${8 + o(19)} + C ${8 + o(20)} ${5.5 + o(21)}, ${9.5 + o(22)} ${4 + o(23)}, ${12 + o(24)} ${4 + o(25)} + M ${4 + o(26)} ${20 + o(27)} + C ${4 + o(28)} ${17 + o(29)}, ${7 + o(30)} ${15 + o(31)}, ${12 + o(32)} ${15 + o(33)} + C ${17 + o(34)} ${15 + o(35)}, ${20 + o(36)} ${17 + o(37)}, ${20 + o(38)} ${20 + o(39)} + `; + }, + + edit: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${17 + o(0)} ${3 + o(1)} + C ${18 + o(2)} ${2 + o(3)}, ${20 + o(4)} ${2 + o(5)}, ${21 + o(6)} ${3 + o(7)} + C ${22 + o(8)} ${4 + o(9)}, ${22 + o(10)} ${6 + o(11)}, ${21 + o(12)} ${7 + o(13)} + L ${7 + o(14)} ${21 + o(15)} + L ${3 + o(16)} ${21 + o(17)} + L ${3 + o(18)} ${17 + o(19)} + L ${17 + o(20)} ${3 + o(21)} + M ${15 + o(22)} ${5 + o(23)} + L ${19 + o(24)} ${9 + o(25)} + `; + }, + + review: (seed) => { + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${10 + o(0)} ${3 + o(1)} + C ${14 + o(2)} ${3 + o(3)}, ${17 + o(4)} ${6 + o(5)}, ${17 + o(6)} ${10 + o(7)} + C ${17 + o(8)} ${14 + o(9)}, ${14 + o(10)} ${17 + o(11)}, ${10 + o(12)} ${17 + o(13)} + C ${6 + o(14)} ${17 + o(15)}, ${3 + o(16)} ${14 + o(17)}, ${3 + o(18)} ${10 + o(19)} + C ${3 + o(20)} ${6 + o(21)}, ${6 + o(22)} ${3 + o(23)}, ${10 + o(24)} ${3 + o(25)} + M ${10 + o(26)} ${7 + o(27)} + L ${10 + o(28)} ${10 + o(29)} + L ${13 + o(30)} ${10 + o(31)} + M ${15 + o(32)} ${15 + o(33)} + L ${21 + o(34)} ${21 + o(35)} + `; + }, + + inbox: (seed) => { + // Empty inbox tray + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${3 + o(0)} ${9 + o(1)} + L ${3 + o(2)} ${19 + o(3)} + C ${3 + o(4)} ${20.5 + o(5)}, ${4.5 + o(6)} ${21 + o(7)}, ${6 + o(8)} ${21 + o(9)} + L ${18 + o(10)} ${21 + o(11)} + C ${19.5 + o(12)} ${21 + o(13)}, ${21 + o(14)} ${20.5 + o(15)}, ${21 + o(16)} ${19 + o(17)} + L ${21 + o(18)} ${9 + o(19)} + M ${8 + o(20)} ${9 + o(21)} + L ${8 + o(22)} ${13 + o(23)} + L ${16 + o(24)} ${13 + o(25)} + L ${16 + o(26)} ${9 + o(27)} + M ${3 + o(28)} ${13 + o(29)} + L ${8 + o(30)} ${13 + o(31)} + M ${16 + o(32)} ${13 + o(33)} + L ${21 + o(34)} ${13 + o(35)} + `; + }, + + error: (seed) => { + // Warning triangle with exclamation + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${3 + o(1)} + L ${21 + o(2)} ${19 + o(3)} + C ${21.5 + o(4)} ${20 + o(5)}, ${21 + o(6)} ${21 + o(7)}, ${20 + o(8)} ${21 + o(9)} + L ${4 + o(10)} ${21 + o(11)} + C ${3 + o(12)} ${21 + o(13)}, ${2.5 + o(14)} ${20 + o(15)}, ${3 + o(16)} ${19 + o(17)} + Z + M ${12 + o(18)} ${9 + o(19)} + L ${12 + o(20)} ${14 + o(21)} + M ${12 + o(22)} ${17 + o(23)} + L ${12 + o(24)} ${17.5 + o(25)} + `; + }, + + test: (seed) => { + // Beaker/flask + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${9 + o(0)} ${3 + o(1)} + L ${15 + o(2)} ${3 + o(3)} + M ${9 + o(4)} ${3 + o(5)} + L ${9 + o(6)} ${9 + o(7)} + L ${4 + o(8)} ${19 + o(9)} + C ${3.5 + o(10)} ${20 + o(11)}, ${4 + o(12)} ${21 + o(13)}, ${5 + o(14)} ${21 + o(15)} + L ${19 + o(16)} ${21 + o(17)} + C ${20 + o(18)} ${21 + o(19)}, ${20.5 + o(20)} ${20 + o(21)}, ${20 + o(22)} ${19 + o(23)} + L ${15 + o(24)} ${9 + o(25)} + L ${15 + o(26)} ${3 + o(27)} + M ${7 + o(28)} ${15 + o(29)} + L ${17 + o(30)} ${15 + o(31)} + `; + }, + + folderOpen: (seed) => { + // Open folder + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${4 + o(0)} ${4 + o(1)} + C ${3 + o(2)} ${4 + o(3)}, ${2 + o(4)} ${5 + o(5)}, ${2 + o(6)} ${6 + o(7)} + L ${2 + o(8)} ${9 + o(9)} + L ${22 + o(10)} ${9 + o(11)} + L ${22 + o(12)} ${6 + o(13)} + C ${22 + o(14)} ${5 + o(15)}, ${21 + o(16)} ${4 + o(17)}, ${20 + o(18)} ${4 + o(19)} + L ${10 + o(20)} ${4 + o(21)} + L ${8 + o(22)} ${2 + o(23)} + L ${4 + o(24)} ${4 + o(25)} + M ${2 + o(26)} ${9 + o(27)} + L ${4 + o(28)} ${20 + o(29)} + C ${4 + o(30)} ${21 + o(31)}, ${5 + o(32)} ${21 + o(33)}, ${6 + o(34)} ${21 + o(35)} + L ${20 + o(36)} ${21 + o(37)} + C ${21 + o(38)} ${21 + o(39)}, ${22 + o(40)} ${20 + o(41)}, ${22 + o(42)} ${19 + o(43)} + L ${24 + o(44)} ${9 + o(45)} + `; + }, + + file: (seed) => { + // Generic file + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${2 + o(1)} + C ${5 + o(2)} ${2 + o(3)}, ${4 + o(4)} ${3 + o(5)}, ${4 + o(6)} ${4 + o(7)} + L ${4 + o(8)} ${20 + o(9)} + C ${4 + o(10)} ${21 + o(11)}, ${5 + o(12)} ${22 + o(13)}, ${6 + o(14)} ${22 + o(15)} + L ${18 + o(16)} ${22 + o(17)} + C ${19 + o(18)} ${22 + o(19)}, ${20 + o(20)} ${21 + o(21)}, ${20 + o(22)} ${20 + o(23)} + L ${20 + o(24)} ${8 + o(25)} + L ${14 + o(26)} ${2 + o(27)} + Z + M ${14 + o(28)} ${2 + o(29)} + L ${14 + o(30)} ${8 + o(31)} + L ${20 + o(32)} ${8 + o(33)} + `; + }, + + fileCode: (seed) => { + // Code file with brackets + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${2 + o(1)} + C ${5 + o(2)} ${2 + o(3)}, ${4 + o(4)} ${3 + o(5)}, ${4 + o(6)} ${4 + o(7)} + L ${4 + o(8)} ${20 + o(9)} + C ${4 + o(10)} ${21 + o(11)}, ${5 + o(12)} ${22 + o(13)}, ${6 + o(14)} ${22 + o(15)} + L ${18 + o(16)} ${22 + o(17)} + C ${19 + o(18)} ${22 + o(19)}, ${20 + o(20)} ${21 + o(21)}, ${20 + o(22)} ${20 + o(23)} + L ${20 + o(24)} ${8 + o(25)} + L ${14 + o(26)} ${2 + o(27)} + Z + M ${9 + o(28)} ${13 + o(29)} + L ${7 + o(30)} ${15 + o(31)} + L ${9 + o(32)} ${17 + o(33)} + M ${15 + o(34)} ${13 + o(35)} + L ${17 + o(36)} ${15 + o(37)} + L ${15 + o(38)} ${17 + o(39)} + `; + }, + + fileData: (seed) => { + // Data file with brackets + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${2 + o(1)} + C ${5 + o(2)} ${2 + o(3)}, ${4 + o(4)} ${3 + o(5)}, ${4 + o(6)} ${4 + o(7)} + L ${4 + o(8)} ${20 + o(9)} + C ${4 + o(10)} ${21 + o(11)}, ${5 + o(12)} ${22 + o(13)}, ${6 + o(14)} ${22 + o(15)} + L ${18 + o(16)} ${22 + o(17)} + C ${19 + o(18)} ${22 + o(19)}, ${20 + o(20)} ${21 + o(21)}, ${20 + o(22)} ${20 + o(23)} + L ${20 + o(24)} ${8 + o(25)} + L ${14 + o(26)} ${2 + o(27)} + Z + M ${8 + o(28)} ${12 + o(29)} + L ${8 + o(30)} ${18 + o(31)} + M ${12 + o(32)} ${14 + o(33)} + L ${12 + o(34)} ${18 + o(35)} + M ${16 + o(36)} ${12 + o(37)} + L ${16 + o(38)} ${18 + o(39)} + `; + }, + + fileDoc: (seed) => { + // Document file with lines + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${2 + o(1)} + C ${5 + o(2)} ${2 + o(3)}, ${4 + o(4)} ${3 + o(5)}, ${4 + o(6)} ${4 + o(7)} + L ${4 + o(8)} ${20 + o(9)} + C ${4 + o(10)} ${21 + o(11)}, ${5 + o(12)} ${22 + o(13)}, ${6 + o(14)} ${22 + o(15)} + L ${18 + o(16)} ${22 + o(17)} + C ${19 + o(18)} ${22 + o(19)}, ${20 + o(20)} ${21 + o(21)}, ${20 + o(22)} ${20 + o(23)} + L ${20 + o(24)} ${8 + o(25)} + L ${14 + o(26)} ${2 + o(27)} + Z + M ${8 + o(28)} ${13 + o(29)} + L ${16 + o(30)} ${13 + o(31)} + M ${8 + o(32)} ${17 + o(33)} + L ${14 + o(34)} ${17 + o(35)} + `; + }, + + fileStyle: (seed) => { + // Style file with palette dots + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${2 + o(1)} + C ${5 + o(2)} ${2 + o(3)}, ${4 + o(4)} ${3 + o(5)}, ${4 + o(6)} ${4 + o(7)} + L ${4 + o(8)} ${20 + o(9)} + C ${4 + o(10)} ${21 + o(11)}, ${5 + o(12)} ${22 + o(13)}, ${6 + o(14)} ${22 + o(15)} + L ${18 + o(16)} ${22 + o(17)} + C ${19 + o(18)} ${22 + o(19)}, ${20 + o(20)} ${21 + o(21)}, ${20 + o(22)} ${20 + o(23)} + L ${20 + o(24)} ${8 + o(25)} + L ${14 + o(26)} ${2 + o(27)} + Z + M ${9 + o(28)} ${14 + o(29)} + C ${9.5 + o(30)} ${13.5 + o(31)}, ${10.5 + o(32)} ${13.5 + o(33)}, ${11 + o(34)} ${14 + o(35)} + M ${13 + o(36)} ${14 + o(37)} + C ${13.5 + o(38)} ${13.5 + o(39)}, ${14.5 + o(40)} ${13.5 + o(41)}, ${15 + o(42)} ${14 + o(43)} + M ${11 + o(44)} ${17 + o(45)} + C ${11.5 + o(46)} ${16.5 + o(47)}, ${12.5 + o(48)} ${16.5 + o(49)}, ${13 + o(50)} ${17 + o(51)} + `; + }, + + fileWeb: (seed) => { + // Web file with globe + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${2 + o(1)} + C ${5 + o(2)} ${2 + o(3)}, ${4 + o(4)} ${3 + o(5)}, ${4 + o(6)} ${4 + o(7)} + L ${4 + o(8)} ${20 + o(9)} + C ${4 + o(10)} ${21 + o(11)}, ${5 + o(12)} ${22 + o(13)}, ${6 + o(14)} ${22 + o(15)} + L ${18 + o(16)} ${22 + o(17)} + C ${19 + o(18)} ${22 + o(19)}, ${20 + o(20)} ${21 + o(21)}, ${20 + o(22)} ${20 + o(23)} + L ${20 + o(24)} ${8 + o(25)} + L ${14 + o(26)} ${2 + o(27)} + Z + M ${12 + o(28)} ${11 + o(29)} + C ${14.5 + o(30)} ${11 + o(31)}, ${16 + o(32)} ${13 + o(33)}, ${16 + o(34)} ${15.5 + o(35)} + C ${16 + o(36)} ${18 + o(37)}, ${14.5 + o(38)} ${19 + o(39)}, ${12 + o(40)} ${19 + o(41)} + C ${9.5 + o(42)} ${19 + o(43)}, ${8 + o(44)} ${18 + o(45)}, ${8 + o(46)} ${15.5 + o(47)} + C ${8 + o(48)} ${13 + o(49)}, ${9.5 + o(50)} ${11 + o(51)}, ${12 + o(52)} ${11 + o(53)} + M ${8 + o(54)} ${15 + o(55)} + L ${16 + o(56)} ${15 + o(57)} + `; + }, + + fileMedia: (seed) => { + // Media file with image + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${2 + o(1)} + C ${5 + o(2)} ${2 + o(3)}, ${4 + o(4)} ${3 + o(5)}, ${4 + o(6)} ${4 + o(7)} + L ${4 + o(8)} ${20 + o(9)} + C ${4 + o(10)} ${21 + o(11)}, ${5 + o(12)} ${22 + o(13)}, ${6 + o(14)} ${22 + o(15)} + L ${18 + o(16)} ${22 + o(17)} + C ${19 + o(18)} ${22 + o(19)}, ${20 + o(20)} ${21 + o(21)}, ${20 + o(22)} ${20 + o(23)} + L ${20 + o(24)} ${8 + o(25)} + L ${14 + o(26)} ${2 + o(27)} + Z + M ${8 + o(28)} ${18 + o(29)} + L ${10 + o(30)} ${15 + o(31)} + L ${12 + o(32)} ${17 + o(33)} + L ${14 + o(34)} ${14 + o(35)} + L ${16 + o(36)} ${18 + o(37)} + M ${15 + o(38)} ${12 + o(39)} + C ${15.5 + o(40)} ${11.5 + o(41)}, ${16.5 + o(42)} ${11.5 + o(43)}, ${17 + o(44)} ${12 + o(45)} + `; + }, + + fileConfig: (seed) => { + // Config file with gear + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${2 + o(1)} + C ${5 + o(2)} ${2 + o(3)}, ${4 + o(4)} ${3 + o(5)}, ${4 + o(6)} ${4 + o(7)} + L ${4 + o(8)} ${20 + o(9)} + C ${4 + o(10)} ${21 + o(11)}, ${5 + o(12)} ${22 + o(13)}, ${6 + o(14)} ${22 + o(15)} + L ${18 + o(16)} ${22 + o(17)} + C ${19 + o(18)} ${22 + o(19)}, ${20 + o(20)} ${21 + o(21)}, ${20 + o(22)} ${20 + o(23)} + L ${20 + o(24)} ${8 + o(25)} + L ${14 + o(26)} ${2 + o(27)} + Z + M ${12 + o(28)} ${13 + o(29)} + C ${13 + o(30)} ${13 + o(31)}, ${14 + o(32)} ${14 + o(33)}, ${14 + o(34)} ${15 + o(35)} + C ${14 + o(36)} ${16 + o(37)}, ${13 + o(38)} ${17 + o(39)}, ${12 + o(40)} ${17 + o(41)} + C ${11 + o(42)} ${17 + o(43)}, ${10 + o(44)} ${16 + o(45)}, ${10 + o(46)} ${15 + o(47)} + C ${10 + o(48)} ${14 + o(49)}, ${11 + o(50)} ${13 + o(51)}, ${12 + o(52)} ${13 + o(53)} + M ${12 + o(54)} ${11 + o(55)} + L ${12 + o(56)} ${12 + o(57)} + M ${12 + o(58)} ${18 + o(59)} + L ${12 + o(60)} ${19 + o(61)} + M ${9 + o(62)} ${15 + o(63)} + L ${8 + o(64)} ${15 + o(65)} + M ${15 + o(66)} ${15 + o(67)} + L ${16 + o(68)} ${15 + o(69)} + `; + }, + + close: (seed) => { + // X mark / close + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${6 + o(1)} + L ${18 + o(2)} ${18 + o(3)} + M ${18 + o(4)} ${6 + o(5)} + L ${6 + o(6)} ${18 + o(7)} + `; + }, + + lightbulb: (seed) => { + // Lightbulb / tip / idea + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${2 + o(1)} + C ${8 + o(2)} ${2 + o(3)}, ${5 + o(4)} ${5 + o(5)}, ${5 + o(6)} ${9 + o(7)} + C ${5 + o(8)} ${11 + o(9)}, ${6 + o(10)} ${13 + o(11)}, ${8 + o(12)} ${15 + o(13)} + L ${8 + o(14)} ${17 + o(15)} + L ${16 + o(16)} ${17 + o(17)} + L ${16 + o(18)} ${15 + o(19)} + C ${18 + o(20)} ${13 + o(21)}, ${19 + o(22)} ${11 + o(23)}, ${19 + o(24)} ${9 + o(25)} + C ${19 + o(26)} ${5 + o(27)}, ${16 + o(28)} ${2 + o(29)}, ${12 + o(30)} ${2 + o(31)} + M ${9 + o(32)} ${21 + o(33)} + L ${15 + o(34)} ${21 + o(35)} + M ${10 + o(36)} ${17 + o(37)} + L ${10 + o(38)} ${19 + o(39)} + M ${14 + o(40)} ${17 + o(41)} + L ${14 + o(42)} ${19 + o(43)} + `; + }, + + warning: (seed) => { + // Warning / caution diamond + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${12 + o(0)} ${3 + o(1)} + L ${21 + o(2)} ${12 + o(3)} + L ${12 + o(4)} ${21 + o(5)} + L ${3 + o(6)} ${12 + o(7)} + Z + M ${12 + o(8)} ${8 + o(9)} + L ${12 + o(10)} ${13 + o(11)} + M ${12 + o(12)} ${16 + o(13)} + L ${12 + o(14)} ${16.5 + o(15)} + `; + }, + + chevronDown: (seed) => { + // Dropdown chevron + const o = (i: number) => generateOrganicOffset(seed, i); + return ` + M ${6 + o(0)} ${9 + o(1)} + L ${12 + o(2)} ${15 + o(3)} + L ${18 + o(4)} ${9 + o(5)} + `; + }, +}; + +/** + * PaintDaubeIcon - Procedural organic SVG icon component + * + * Generates unique, brand-consistent icons with organic paint daube aesthetic. + * Each icon has slight variations based on the seed value, making them feel + * hand-crafted rather than machine-perfect. + */ +export function PaintDaubeIcon({ + variant, + color = 'coral', + size = 24, + turbulence = 0.3, + seed = 42, +}: PaintDaubeIconProps) { + const filterId = `paint-daube-${variant}-${seed}`; + const strokeColor = BRAND_COLORS[color]; + const pathData = ICON_PATHS[variant](seed); + + return ( + + + + + + + + + ); +} + +/** + * Preset icon components for common use cases + */ +export const AgentIcon = (props: Omit) => ( + +); + +export const MobileIcon = (props: Omit) => ( + +); + +export const SecurityIcon = (props: Omit) => ( + +); + +export const LightningIcon = (props: Omit) => ( + +); + +export const SuccessIcon = (props: Omit) => ( + +); + +export const CelebrateIcon = (props: Omit) => ( + +); + +export const GitIcon = (props: Omit) => ( + +); + +export const SettingsIcon = (props: Omit) => ( + +); + +export const ChatIcon = (props: Omit) => ( + +); + +export const CodeIcon = (props: Omit) => ( + +); + +export const FolderIcon = (props: Omit) => ( + +); + +export const StarIcon = (props: Omit) => ( + +); + +export const KeyIcon = (props: Omit) => ( + +); + +export const GitHubIcon = (props: Omit) => ( + +); + +export const ThumbIcon = (props: Omit) => ( + +); + +export const HomeIcon = (props: Omit) => ( + +); + +export const TasksIcon = (props: Omit) => ( + +); + +export const SearchIcon = (props: Omit) => ( + +); + +export const BellIcon = (props: Omit) => ( + +); + +export const LinkIcon = (props: Omit) => ( + +); + +export const BrainIcon = (props: Omit) => ( + +); + +export const PaletteIcon = (props: Omit) => ( + +); + +export const VibrateIcon = (props: Omit) => ( + +); + +export const KeyboardIcon = (props: Omit) => ( + +); + +export const BranchIcon = (props: Omit) => ( + +); + +export const BookIcon = (props: Omit) => ( + +); + +export const SupportIcon = (props: Omit) => ( + +); + +export const LegalIcon = (props: Omit) => ( + +); + +export const InfoIcon = (props: Omit) => ( + +); + +export const UserIcon = (props: Omit) => ( + +); + +export const EditIcon = (props: Omit) => ( + +); + +export const ReviewIcon = (props: Omit) => ( + +); + +export const InboxIcon = (props: Omit) => ( + +); + +export const ErrorIcon = (props: Omit) => ( + +); + +export const TestIcon = (props: Omit) => ( + +); + +export const FolderOpenIcon = (props: Omit) => ( + +); + +export const FileIcon = (props: Omit) => ( + +); + +export const FileCodeIcon = (props: Omit) => ( + +); + +export const FileDataIcon = (props: Omit) => ( + +); + +export const FileDocIcon = (props: Omit) => ( + +); + +export const FileStyleIcon = (props: Omit) => ( + +); + +export const FileWebIcon = (props: Omit) => ( + +); + +export const FileMediaIcon = (props: Omit) => ( + +); + +export const FileConfigIcon = (props: Omit) => ( + +); + +export const CloseIcon = (props: Omit) => ( + +); + +export const LightbulbIcon = (props: Omit) => ( + +); + +export const WarningIcon = (props: Omit) => ( + +); + +export const ChevronDownIcon = (props: Omit) => ( + +); + +export default PaintDaubeIcon; diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts new file mode 100644 index 00000000..787f2351 --- /dev/null +++ b/src/components/icons/index.ts @@ -0,0 +1,76 @@ +/** + * ThumbCode Icon System + * + * Procedural paint daube SVG icons that embody the "Warm Technical" brand. + * All icons use organic shapes, feTurbulence filters, and brand colors. + * + * Usage: + * ```tsx + * import { AgentIcon, MobileIcon, SecurityIcon } from '@/components/icons'; + * + * + * + * // Different seed = unique variation + * ``` + * + * @see PaintDaubeIcon for full documentation + */ + +export type { IconColor, IconVariant } from './PaintDaubeIcon'; +export { + // Preset icons - Core + AgentIcon, + BellIcon, + BookIcon, + BRAND_COLORS, + BrainIcon, + BranchIcon, + CelebrateIcon, + ChatIcon, + // Preset icons - UI controls + ChevronDownIcon, + CloseIcon, + CodeIcon, + EditIcon, + ErrorIcon, + FileCodeIcon, + FileConfigIcon, + FileDataIcon, + FileDocIcon, + FileIcon, + FileMediaIcon, + FileStyleIcon, + FileWebIcon, + FolderIcon, + // Preset icons - File types + FolderOpenIcon, + GitHubIcon, + GitIcon, + // Preset icons - Navigation & UI + HomeIcon, + // Preset icons - Empty states & feedback + InboxIcon, + InfoIcon, + KeyboardIcon, + KeyIcon, + LegalIcon, + LightbulbIcon, + LightningIcon, + LinkIcon, + MobileIcon, + PaintDaubeIcon, + PaletteIcon, + ReviewIcon, + SearchIcon, + SecurityIcon, + SettingsIcon, + StarIcon, + SuccessIcon, + SupportIcon, + TasksIcon, + TestIcon, + ThumbIcon, + UserIcon, + VibrateIcon, + WarningIcon, +} from './PaintDaubeIcon';