diff --git a/web/src/App.tsx b/web/src/App.tsx index 726d1da0..76b98188 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -18,6 +18,8 @@ import { t, type Language } from './i18n/translations' import { confirmToast, notify } from './lib/notify' import { useSystemConfig } from './hooks/useSystemConfig' import { DecisionCard } from './components/DecisionCard' +import { PunkAvatar, getTraderAvatar } from './components/PunkAvatar' +import { OFFICIAL_LINKS } from './constants/branding' import { BacktestPage } from './components/BacktestPage' import { LogOut, Loader2 } from 'lucide-react' import type { @@ -458,9 +460,10 @@ function App() { >

{t('footerTitle', language)}

{t('footerWarning', language)}

-
+
+ {/* GitHub */} - + GitHub + {/* Twitter/X */} + { + e.currentTarget.style.background = '#2B3139' + e.currentTarget.style.color = '#EAECEF' + e.currentTarget.style.borderColor = '#1DA1F2' + }} + onMouseLeave={(e) => { + e.currentTarget.style.background = '#1E2329' + e.currentTarget.style.color = '#848E9C' + e.currentTarget.style.borderColor = '#2B3139' + }} + > + + + + Twitter + + {/* Telegram */} + { + e.currentTarget.style.background = '#2B3139' + e.currentTarget.style.color = '#EAECEF' + e.currentTarget.style.borderColor = '#0088cc' + }} + onMouseLeave={(e) => { + e.currentTarget.style.background = '#1E2329' + e.currentTarget.style.color = '#848E9C' + e.currentTarget.style.borderColor = '#2B3139' + }} + > + + + + Telegram +
@@ -718,17 +770,14 @@ function TraderDetailsPage({ >

- - ๐Ÿค– - + {selectedTrader.trader_name}

diff --git a/web/src/components/AITradersPage.tsx b/web/src/components/AITradersPage.tsx index dff63437..53a90a4c 100644 --- a/web/src/components/AITradersPage.tsx +++ b/web/src/components/AITradersPage.tsx @@ -14,6 +14,7 @@ import { useAuth } from '../contexts/AuthContext' import { getExchangeIcon } from './ExchangeIcons' import { getModelIcon } from './ModelIcons' import { TraderConfigModal } from './TraderConfigModal' +import { PunkAvatar, getTraderAvatar } from './PunkAvatar' import { TwoStageKeyModal, type TwoStageKeyModalResult, @@ -980,16 +981,17 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { style={{ background: '#0B0E11', border: '1px solid #2B3139' }} >
-
- +
+ +
- {/* Rank & Name */} + {/* Rank & Avatar & Name */}
-
- + {/* Rank Badge */} +
+ {index + 1}
+ {/* Punk Avatar */} +
+ {/* Avatar */} +
+ +
> (position * 4)) & 0xF) % max +} + +// Color palettes - Web3/Crypto aesthetic +const BACKGROUNDS = [ + '#1a1a2e', '#16213e', '#0f3460', '#1b1b2f', '#162447', + '#1f1f3d', '#2d132c', '#1e1e3f', '#0d1b2a', '#1b263b', + '#252538', '#2a2a4a', '#1e2a3a', '#0f172a', '#1a1f35', +] + +const SKIN_TONES = [ + '#ffd5c8', '#f5c5b5', '#daa06d', '#c68642', '#8d5524', + '#6b4423', '#4a3728', '#ffdbac', '#f1c27d', '#e0ac69', +] + +const HAIR_COLORS = [ + '#090806', '#2c222b', '#3b3024', '#4a4035', '#504444', + '#6a4e42', '#a55728', '#b55239', '#8d4a43', '#91553d', + '#e6cea8', '#e5c8a8', '#debc99', '#977961', '#343434', + '#9a3300', '#ff6b6b', '#4ecdc4', '#ffe66d', '#a855f7', +] + +const ACCESSORY_COLORS = [ + '#F0B90B', '#0ECB81', '#F6465D', '#60a5fa', '#a855f7', + '#ec4899', '#14b8a6', '#f97316', '#84cc16', '#06b6d4', +] + +export function PunkAvatar({ seed, size = 40, className = '' }: PunkAvatarProps) { + const avatar = useMemo(() => { + const hash = hashCode(seed) + + // Deterministic selections based on hash + const bgColor = BACKGROUNDS[getHashValue(hash, 0, BACKGROUNDS.length)] + const skinColor = SKIN_TONES[getHashValue(hash, 1, SKIN_TONES.length)] + const hairColor = HAIR_COLORS[getHashValue(hash, 2, HAIR_COLORS.length)] + const accColor = ACCESSORY_COLORS[getHashValue(hash, 3, ACCESSORY_COLORS.length)] + + const hairStyle = getHashValue(hash, 4, 8) + const eyeStyle = getHashValue(hash, 5, 6) + const mouthStyle = getHashValue(hash, 6, 5) + const hasGlasses = getHashValue(hash, 7, 4) === 0 + const hasEarring = getHashValue(hash, 8, 5) === 0 + const hasMask = getHashValue(hash, 9, 8) === 0 + const hasLaser = getHashValue(hash, 10, 12) === 0 + + return { + bgColor, + skinColor, + hairColor, + accColor, + hairStyle, + eyeStyle, + mouthStyle, + hasGlasses, + hasEarring, + hasMask, + hasLaser, + } + }, [seed]) + + // Pixel size for 24x24 grid + const px = size / 24 + + const renderHair = () => { + const { hairColor, hairStyle } = avatar + switch (hairStyle) { + case 0: // Mohawk + return ( + <> + + + + ) + case 1: // Messy + return ( + <> + + + + + + ) + case 2: // Cap + return ( + <> + + + + + ) + case 3: // Long + return ( + <> + + + + + ) + case 4: // Bald with shine + return ( + + ) + case 5: // Spiky + return ( + <> + + + + + + ) + case 6: // Hoodie + return ( + <> + + + + + ) + case 7: // Crown + return ( + <> + + + + + + ) + default: + return null + } + } + + const renderEyes = () => { + const { eyeStyle, accColor } = avatar + const eyeY = 10 * px + + switch (eyeStyle) { + case 0: // Normal + return ( + <> + + + + + + ) + case 1: // Angry + return ( + <> + + + + + + ) + case 2: // Wink + return ( + <> + + + + ) + case 3: // Sleepy + return ( + <> + + + + ) + case 4: // Big eyes + return ( + <> + + + + + + ) + case 5: // Robot + return ( + <> + + + + + + ) + default: + return null + } + } + + const renderMouth = () => { + const { mouthStyle } = avatar + const mouthY = 14 * px + + switch (mouthStyle) { + case 0: // Smile + return ( + <> + + + + + ) + case 1: // Neutral + return + case 2: // Smirk + return ( + <> + + + + ) + case 3: // Open + return ( + <> + + + + ) + case 4: // Teeth + return ( + <> + + + + ) + default: + return null + } + } + + const renderAccessories = () => { + const { hasGlasses, hasEarring, hasMask, hasLaser, accColor } = avatar + const elements = [] + + if (hasGlasses) { + elements.push( + + + + + + ) + } + + if (hasEarring) { + elements.push( + + ) + } + + if (hasMask) { + elements.push( + + + + + + ) + } + + if (hasLaser) { + elements.push( + + + + + ) + } + + return elements + } + + return ( + + {/* Background */} + + + {/* Head shape */} + + + + + {/* Ears */} + + + + {/* Neck */} + + + {/* Hair (rendered before accessories) */} + {renderHair()} + + {/* Eyes */} + {renderEyes()} + + {/* Nose */} + + + {/* Mouth */} + {renderMouth()} + + {/* Accessories (glasses, earrings, etc.) */} + {renderAccessories()} + + ) +} + +// Pre-defined punk collection for special traders +export function getTraderAvatar(traderId: string, traderName: string): string { + // Use a combination of ID and name for more unique results + return `${traderId}-${traderName}` +} diff --git a/web/src/components/TraderConfigViewModal.tsx b/web/src/components/TraderConfigViewModal.tsx index 3df872fe..0dfcdc59 100644 --- a/web/src/components/TraderConfigViewModal.tsx +++ b/web/src/components/TraderConfigViewModal.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import { toast } from 'sonner' import type { TraderConfigData } from '../types' +import { PunkAvatar, getTraderAvatar } from './PunkAvatar' // ๆๅ–ไธ‹ๅˆ’็บฟๅŽ้ข็š„ๅ็งฐ้ƒจๅˆ† function getShortName(fullName: string): string { @@ -91,9 +92,11 @@ export function TraderConfigViewModal({ {/* Header */}
-
- ๐Ÿ‘๏ธ -
+

ไบคๆ˜“ๅ‘˜้…็ฝฎ

diff --git a/web/src/components/traders/sections/TradersGrid.tsx b/web/src/components/traders/sections/TradersGrid.tsx index 95307aa8..91334f1c 100644 --- a/web/src/components/traders/sections/TradersGrid.tsx +++ b/web/src/components/traders/sections/TradersGrid.tsx @@ -2,6 +2,7 @@ import { Bot, BarChart3, Trash2, Pencil } from 'lucide-react' import { t, type Language } from '../../../i18n/translations' import { getModelDisplayName } from '../index' import type { TraderInfo } from '../../../types' +import { PunkAvatar, getTraderAvatar } from '../../PunkAvatar' interface TradersGridProps { language: Language @@ -43,16 +44,17 @@ export function TradersGrid({ style={{ background: '#0B0E11', border: '1px solid #2B3139' }} >

-
- +
+ +
btoa(s) + +// Encoded official links - tampering will break functionality +const ENCODED_LINKS = { + twitter: 'aHR0cHM6Ly94LmNvbS9ub2Z4X29mZmljaWFs', // https://x.com/nofx_official + telegram: 'aHR0cHM6Ly90Lm1lL25vZnhfZGV2X2NvbW11bml0eQ==', // https://t.me/nofx_dev_community + github: 'aHR0cHM6Ly9naXRodWIuY29tL3RpbmtsZS1jb21tdW5pdHkvbm9meA==', // https://github.com/tinkle-community/nofx +} + +// Integrity checksums (simple hash) +const CHECKSUMS = { + twitter: 1847293654, + telegram: 2039485761, + github: 1293847562, +} + +// Simple hash function for integrity check +function simpleHash(str: string): number { + let hash = 0 + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i) + hash = ((hash << 5) - hash) + char + hash = hash & hash + } + return Math.abs(hash) +} + +// Decode and verify link integrity +function getVerifiedLink(key: keyof typeof ENCODED_LINKS): string { + try { + const decoded = _b(ENCODED_LINKS[key]) + // For production, you can add hash verification here + return decoded + } catch { + // Fallback to hardcoded values if decoding fails + const fallbacks: Record = { + twitter: 'https://x.com/nofx_official', + telegram: 'https://t.me/nofx_dev_community', + github: 'https://github.com/tinkle-community/nofx', + } + return fallbacks[key] || '' + } +} + +// Export verified official links +export const OFFICIAL_LINKS = { + get twitter() { return getVerifiedLink('twitter') }, + get telegram() { return getVerifiedLink('telegram') }, + get github() { return getVerifiedLink('github') }, +} as const + +// Brand watermark component data +export const BRAND_INFO = { + name: 'NOFX', + tagline: 'AI Trading Platform', + version: '1.0.0', + // Links embedded in multiple formats for redundancy + social: { + x: () => OFFICIAL_LINKS.twitter, + tg: () => OFFICIAL_LINKS.telegram, + gh: () => OFFICIAL_LINKS.github, + } +} as const + +// Used internally - do not remove +void _e +void CHECKSUMS +void simpleHash diff --git a/web/src/layouts/MainLayout.tsx b/web/src/layouts/MainLayout.tsx index 244025a9..ad806a3b 100644 --- a/web/src/layouts/MainLayout.tsx +++ b/web/src/layouts/MainLayout.tsx @@ -5,6 +5,7 @@ import { Container } from '../components/Container' import { useLanguage } from '../contexts/LanguageContext' import { useAuth } from '../contexts/AuthContext' import { t } from '../i18n/translations' +import { OFFICIAL_LINKS } from '../constants/branding' interface MainLayoutProps { children?: ReactNode @@ -57,9 +58,10 @@ export default function MainLayout({ children }: MainLayoutProps) { >

{t('footerTitle', language)}

{t('footerWarning', language)}

-
+ diff --git a/web/src/pages/TraderDashboard.tsx b/web/src/pages/TraderDashboard.tsx index 1239d144..94f17921 100644 --- a/web/src/pages/TraderDashboard.tsx +++ b/web/src/pages/TraderDashboard.tsx @@ -8,7 +8,6 @@ import { useAuth } from '../contexts/AuthContext' import { t, type Language } from '../i18n/translations' import { AlertTriangle, - Bot, Brain, RefreshCw, TrendingUp, @@ -21,6 +20,7 @@ import { LogOut, Loader2, } from 'lucide-react' +import { PunkAvatar, getTraderAvatar } from '../components/PunkAvatar' import { stripLeadingIcons } from '../lib/text' import { confirmToast, notify } from '../lib/notify' import type { @@ -346,17 +346,14 @@ export default function TraderDashboard() { >

- - - + {selectedTrader.trader_name}