feat: redesign landing page UI and improve header

This commit is contained in:
tinkle-community
2025-12-09 13:59:14 +08:00
parent 4291b80273
commit 48792907b3
12 changed files with 1204 additions and 738 deletions

View File

@@ -197,8 +197,12 @@ func (s *TraderStore) UpdateCustomPrompt(userID, id string, customPrompt string,
return err return err
} }
// Delete deletes trader // Delete deletes trader and associated data
func (s *TraderStore) Delete(userID, id string) error { func (s *TraderStore) Delete(userID, id string) error {
// Delete associated equity snapshots first
_, _ = s.db.Exec(`DELETE FROM trader_equity_snapshots WHERE trader_id = ?`, id)
// Delete the trader
_, err := s.db.Exec(`DELETE FROM traders WHERE id = ? AND user_id = ?`, id, userID) _, err := s.db.Exec(`DELETE FROM traders WHERE id = ? AND user_id = ?`, id, userID)
return err return err
} }

View File

@@ -34,6 +34,8 @@ import {
BookOpen, BookOpen,
HelpCircle, HelpCircle,
Pencil, Pencil,
UserPlus,
ExternalLink,
} from 'lucide-react' } from 'lucide-react'
import { confirmToast } from '../lib/notify' import { confirmToast } from '../lib/notify'
import { toast } from 'sonner' import { toast } from 'sonner'
@@ -1611,6 +1613,16 @@ function ExchangeConfigModal({
(e) => e.id === selectedExchangeId (e) => e.id === selectedExchangeId
) )
// 交易所注册链接配置
const exchangeRegistrationLinks: Record<string, { url: string; hasReferral?: boolean }> = {
binance: { url: 'https://www.binance.com/join?ref=NOFXAI', hasReferral: true },
okx: { url: 'https://www.okx.com/join/1865360', hasReferral: true },
bybit: { url: 'https://partner.bybit.com/b/83856', hasReferral: true },
hyperliquid: { url: 'https://app.hyperliquid.xyz/join/AITRADING', hasReferral: true },
aster: { url: 'https://www.asterdex.com/en/referral/fdfc0e', hasReferral: true },
lighter: { url: 'https://lighter.xyz', hasReferral: false },
}
// 如果是编辑现有交易所,初始化表单数据 // 如果是编辑现有交易所,初始化表单数据
useEffect(() => { useEffect(() => {
if (editingExchangeId && selectedExchange) { if (editingExchangeId && selectedExchange) {
@@ -1918,6 +1930,35 @@ function ExchangeConfigModal({
</div> </div>
</div> </div>
</div> </div>
{/* 注册链接 */}
{exchangeRegistrationLinks[selectedExchange.id] && (
<a
href={exchangeRegistrationLinks[selectedExchange.id].url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between p-3 rounded-lg transition-all hover:scale-[1.02]"
style={{
background: 'rgba(240, 185, 11, 0.08)',
border: '1px solid rgba(240, 185, 11, 0.2)',
}}
>
<div className="flex items-center gap-2">
<UserPlus className="w-4 h-4" style={{ color: '#F0B90B' }} />
<span className="text-sm" style={{ color: '#EAECEF' }}>
{language === 'zh' ? '还没有交易所账号?点击注册' : "No exchange account? Register here"}
</span>
{exchangeRegistrationLinks[selectedExchange.id].hasReferral && (
<span
className="text-xs px-1.5 py-0.5 rounded"
style={{ background: 'rgba(14, 203, 129, 0.2)', color: '#0ECB81' }}
>
{language === 'zh' ? '折扣优惠' : 'Discount'}
</span>
)}
</div>
<ExternalLink className="w-4 h-4" style={{ color: '#848E9C' }} />
</a>
)}
</div> </div>
)} )}

View File

@@ -5,6 +5,7 @@ import { Menu, X, ChevronDown } from 'lucide-react'
import { t, type Language } from '../i18n/translations' import { t, type Language } from '../i18n/translations'
import { Container } from './Container' import { Container } from './Container'
import { useSystemConfig } from '../hooks/useSystemConfig' import { useSystemConfig } from '../hooks/useSystemConfig'
import { OFFICIAL_LINKS } from '../constants/branding'
type Page = type Page =
| 'competition' | 'competition'
@@ -430,45 +431,77 @@ export default function HeaderBar({
)} )}
</div> </div>
{/* Right Side - Original Navigation Items and Login */} {/* Right Side - Social Links and User Actions */}
<div className="flex items-center gap-6"> <div className="flex items-center gap-4">
{/* Only show original navigation items on home page */} {/* Social Links - Always visible */}
{isHomePage && <div className="flex items-center gap-1">
[ {/* GitHub */}
{ key: 'features', label: t('features', language) }, <a
{ key: 'howItWorks', label: t('howItWorks', language) }, href={OFFICIAL_LINKS.github}
{ key: 'GitHub', label: 'GitHub' }, target="_blank"
{ key: 'community', label: t('community', language) }, rel="noopener noreferrer"
].map((item) => ( className="p-2 rounded-lg transition-all hover:scale-110"
<a style={{ color: '#848E9C' }}
key={item.key} onMouseEnter={(e) => {
href={ e.currentTarget.style.color = '#EAECEF'
item.key === 'GitHub' e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)'
? 'https://github.com/tinkle-community/nofx' }}
: item.key === 'community' onMouseLeave={(e) => {
? 'https://t.me/nofx_dev_community' e.currentTarget.style.color = '#848E9C'
: `#${item.key === 'features' ? 'features' : 'how-it-works'}` e.currentTarget.style.background = 'transparent'
} }}
target={ title="GitHub"
item.key === 'GitHub' || item.key === 'community' >
? '_blank' <svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor">
: undefined <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
} </svg>
rel={ </a>
item.key === 'GitHub' || item.key === 'community' {/* Twitter/X */}
? 'noopener noreferrer' <a
: undefined href={OFFICIAL_LINKS.twitter}
} target="_blank"
className="text-sm transition-colors relative group" rel="noopener noreferrer"
style={{ color: 'var(--brand-light-gray)' }} className="p-2 rounded-lg transition-all hover:scale-110"
> style={{ color: '#848E9C' }}
{item.label} onMouseEnter={(e) => {
<span e.currentTarget.style.color = '#1DA1F2'
className="absolute -bottom-1 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300" e.currentTarget.style.background = 'rgba(29, 161, 242, 0.1)'
style={{ background: 'var(--brand-yellow)' }} }}
/> onMouseLeave={(e) => {
</a> e.currentTarget.style.color = '#848E9C'
))} e.currentTarget.style.background = 'transparent'
}}
title="Twitter"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
</a>
{/* Telegram */}
<a
href={OFFICIAL_LINKS.telegram}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg transition-all hover:scale-110"
style={{ color: '#848E9C' }}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#0088cc'
e.currentTarget.style.background = 'rgba(0, 136, 204, 0.1)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = '#848E9C'
e.currentTarget.style.background = 'transparent'
}}
title="Telegram"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" />
</svg>
</a>
</div>
{/* Divider */}
<div className="h-5 w-px" style={{ background: '#2B3139' }} />
{/* User Info and Actions */} {/* User Info and Actions */}
{isLoggedIn && user ? ( {isLoggedIn && user ? (
@@ -932,28 +965,10 @@ export default function HeaderBar({
[ [
{ key: 'features', label: t('features', language) }, { key: 'features', label: t('features', language) },
{ key: 'howItWorks', label: t('howItWorks', language) }, { key: 'howItWorks', label: t('howItWorks', language) },
{ key: 'GitHub', label: 'GitHub' },
{ key: 'community', label: t('community', language) },
].map((item) => ( ].map((item) => (
<a <a
key={item.key} key={item.key}
href={ href={`#${item.key === 'features' ? 'features' : 'how-it-works'}`}
item.key === 'GitHub'
? 'https://github.com/tinkle-community/nofx'
: item.key === 'community'
? 'https://t.me/nofx_dev_community'
: `#${item.key === 'features' ? 'features' : 'how-it-works'}`
}
target={
item.key === 'GitHub' || item.key === 'community'
? '_blank'
: undefined
}
rel={
item.key === 'GitHub' || item.key === 'community'
? 'noopener noreferrer'
: undefined
}
className="block text-sm py-2" className="block text-sm py-2"
style={{ color: 'var(--brand-light-gray)' }} style={{ color: 'var(--brand-light-gray)' }}
> >
@@ -961,6 +976,43 @@ export default function HeaderBar({
</a> </a>
))} ))}
{/* Social Links - Mobile */}
<div className="py-3 flex items-center gap-3" style={{ borderTop: '1px solid #2B3139' }}>
<a
href={OFFICIAL_LINKS.github}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg"
style={{ color: '#848E9C', background: 'rgba(255, 255, 255, 0.05)' }}
>
<svg width="20" height="20" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
</svg>
</a>
<a
href={OFFICIAL_LINKS.twitter}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg"
style={{ color: '#848E9C', background: 'rgba(255, 255, 255, 0.05)' }}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
</a>
<a
href={OFFICIAL_LINKS.telegram}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg"
style={{ color: '#848E9C', background: 'rgba(255, 255, 255, 0.05)' }}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" />
</svg>
</a>
</div>
{/* Language Toggle */} {/* Language Toggle */}
<div className="py-2"> <div className="py-2">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">

View File

@@ -3,7 +3,7 @@ import type { AIModel, Exchange, CreateTraderRequest, Strategy } from '../types'
import { useLanguage } from '../contexts/LanguageContext' import { useLanguage } from '../contexts/LanguageContext'
import { t } from '../i18n/translations' import { t } from '../i18n/translations'
import { toast } from 'sonner' import { toast } from 'sonner'
import { Pencil, Plus, X as IconX, Sparkles } from 'lucide-react' import { Pencil, Plus, X as IconX, Sparkles, ExternalLink, UserPlus } from 'lucide-react'
import { httpClient } from '../lib/httpClient' import { httpClient } from '../lib/httpClient'
// 提取下划线后面的名称部分 // 提取下划线后面的名称部分
@@ -12,6 +12,16 @@ function getShortName(fullName: string): string {
return parts.length > 1 ? parts[parts.length - 1] : fullName return parts.length > 1 ? parts[parts.length - 1] : fullName
} }
// 交易所注册链接配置
const EXCHANGE_REGISTRATION_LINKS: Record<string, { url: string; hasReferral?: boolean }> = {
binance: { url: 'https://www.binance.com/join?ref=NOFXENG', hasReferral: true },
okx: { url: 'https://www.okx.com/join/1865360', hasReferral: true },
bybit: { url: 'https://partner.bybit.com/b/83856', hasReferral: true },
hyperliquid: { url: 'https://app.hyperliquid.xyz/join/AITRADING', hasReferral: true },
aster: { url: 'https://www.asterdex.com/en/referral/fdfc0e', hasReferral: true },
lighter: { url: 'https://lighter.xyz', hasReferral: false },
}
import type { TraderConfigData } from '../types' import type { TraderConfigData } from '../types'
// 表单内部状态类型 // 表单内部状态类型
@@ -272,6 +282,30 @@ export function TraderConfigModal({
</option> </option>
))} ))}
</select> </select>
{/* Exchange Registration Link */}
{formData.exchange_id && (() => {
// Exchange ID is the exchange type (e.g., "binance", "okx", "aster")
const exchangeType = formData.exchange_id.toLowerCase()
const regLink = EXCHANGE_REGISTRATION_LINKS[exchangeType]
if (!regLink) return null
return (
<a
href={regLink.url}
target="_blank"
rel="noopener noreferrer"
className="mt-2 inline-flex items-center gap-1.5 text-xs text-[#848E9C] hover:text-[#F0B90B] transition-colors"
>
<UserPlus className="w-3.5 h-3.5" />
<span></span>
{regLink.hasReferral && (
<span className="px-1.5 py-0.5 bg-[#F0B90B]/10 text-[#F0B90B] rounded text-[10px]">
</span>
)}
<ExternalLink className="w-3 h-3" />
</a>
)
})()}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,5 @@
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Shield, Target } from 'lucide-react' import { Terminal, Shield, Cpu, BarChart3 } from 'lucide-react'
import AnimatedSection from './AnimatedSection'
import Typewriter from '../Typewriter'
import { t, Language } from '../../i18n/translations' import { t, Language } from '../../i18n/translations'
interface AboutSectionProps { interface AboutSectionProps {
@@ -9,119 +7,151 @@ interface AboutSectionProps {
} }
export default function AboutSection({ language }: AboutSectionProps) { export default function AboutSection({ language }: AboutSectionProps) {
const features = [
{
icon: Shield,
title: language === 'zh' ? '完全自主控制' : 'Full Control',
desc: language === 'zh' ? '自托管,数据安全' : 'Self-hosted, data secure',
},
{
icon: Cpu,
title: language === 'zh' ? '多 AI 支持' : 'Multi-AI Support',
desc: language === 'zh' ? 'DeepSeek, GPT, Claude...' : 'DeepSeek, GPT, Claude...',
},
{
icon: BarChart3,
title: language === 'zh' ? '实时监控' : 'Real-time Monitor',
desc: language === 'zh' ? '可视化交易看板' : 'Visual trading dashboard',
},
]
return ( return (
<AnimatedSection id="about" backgroundColor="var(--brand-dark-gray)"> <section className="py-24 relative overflow-hidden" style={{ background: '#0B0E11' }}>
<div className="max-w-7xl mx-auto"> {/* Background Decoration */}
<div className="grid lg:grid-cols-2 gap-12 items-center"> <div
className="absolute top-0 right-0 w-96 h-96 rounded-full blur-3xl opacity-30"
style={{ background: 'radial-gradient(circle, rgba(240, 185, 11, 0.1) 0%, transparent 70%)' }}
/>
<div className="max-w-6xl mx-auto px-4">
<div className="grid lg:grid-cols-2 gap-16 items-center">
{/* Left Content */}
<motion.div <motion.div
className="space-y-6" initial={{ opacity: 0, x: -30 }}
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
> >
<motion.div <motion.div
className="inline-flex items-center gap-2 px-4 py-2 rounded-full" className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full mb-6"
style={{ style={{
background: 'rgba(240, 185, 11, 0.1)', background: 'rgba(240, 185, 11, 0.1)',
border: '1px solid rgba(240, 185, 11, 0.2)', border: '1px solid rgba(240, 185, 11, 0.2)',
}} }}
whileHover={{ scale: 1.05 }}
> >
<Target <Terminal className="w-4 h-4" style={{ color: '#F0B90B' }} />
className="w-4 h-4" <span className="text-xs font-medium" style={{ color: '#F0B90B' }}>
style={{ color: 'var(--brand-yellow)' }}
/>
<span
className="text-sm font-semibold"
style={{ color: 'var(--brand-yellow)' }}
>
{t('aboutNofx', language)} {t('aboutNofx', language)}
</span> </span>
</motion.div> </motion.div>
<h2 <h2 className="text-4xl lg:text-5xl font-bold mb-6" style={{ color: '#EAECEF' }}>
className="text-4xl font-bold"
style={{ color: 'var(--brand-light-gray)' }}
>
{t('whatIsNofx', language)} {t('whatIsNofx', language)}
</h2> </h2>
<p
className="text-lg leading-relaxed" <p className="text-lg mb-8 leading-relaxed" style={{ color: '#848E9C' }}>
style={{ color: 'var(--text-secondary)' }} {t('nofxNotAnotherBot', language)} {t('nofxDescription1', language)}
>
{t('nofxNotAnotherBot', language)}{' '}
{t('nofxDescription1', language)}{' '}
{t('nofxDescription2', language)}
</p> </p>
<p
className="text-lg leading-relaxed" {/* Feature Pills */}
style={{ color: 'var(--text-secondary)' }} <div className="flex flex-wrap gap-3">
> {features.map((feature, index) => (
{t('nofxDescription3', language)}{' '} <motion.div
{t('nofxDescription4', language)}{' '} key={feature.title}
{t('nofxDescription5', language)} initial={{ opacity: 0, y: 20 }}
</p> whileInView={{ opacity: 1, y: 0 }}
<motion.div viewport={{ once: true }}
className="flex items-center gap-3 pt-4" transition={{ delay: index * 0.1 }}
whileHover={{ x: 5 }} className="flex items-center gap-3 px-4 py-3 rounded-xl"
> style={{
<div background: 'rgba(255, 255, 255, 0.03)',
className="w-12 h-12 rounded-full flex items-center justify-center" border: '1px solid rgba(255, 255, 255, 0.06)',
style={{ background: 'rgba(240, 185, 11, 0.1)' }} }}
>
<Shield
className="w-6 h-6"
style={{ color: 'var(--brand-yellow)' }}
/>
</div>
<div>
<div
className="font-semibold"
style={{ color: 'var(--brand-light-gray)' }}
> >
{t('youFullControl', language)} <div
</div> className="w-10 h-10 rounded-lg flex items-center justify-center"
<div style={{ background: 'rgba(240, 185, 11, 0.1)' }}
className="text-sm" >
style={{ color: 'var(--text-secondary)' }} <feature.icon className="w-5 h-5" style={{ color: '#F0B90B' }} />
> </div>
{t('fullControlDesc', language)} <div>
</div> <div className="text-sm font-semibold" style={{ color: '#EAECEF' }}>
</div> {feature.title}
</motion.div> </div>
<div className="text-xs" style={{ color: '#5E6673' }}>
{feature.desc}
</div>
</div>
</motion.div>
))}
</div>
</motion.div> </motion.div>
<div className="relative"> {/* Right - Terminal */}
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }}
>
<div <div
className="rounded-2xl p-8" className="rounded-2xl overflow-hidden"
style={{ style={{
background: 'var(--brand-black)', background: '#0D1117',
border: '1px solid var(--panel-border)', border: '1px solid rgba(255, 255, 255, 0.1)',
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5)',
}} }}
> >
<Typewriter {/* Terminal Header */}
lines={[ <div
'$ git clone https://github.com/tinkle-community/nofx.git', className="flex items-center gap-2 px-4 py-3"
'$ cd nofx', style={{ background: 'rgba(255, 255, 255, 0.03)', borderBottom: '1px solid rgba(255, 255, 255, 0.06)' }}
'$ chmod +x start.sh', >
'$ ./start.sh start --build', <div className="flex gap-2">
t('startupMessages1', language), <div className="w-3 h-3 rounded-full" style={{ background: '#FF5F56' }} />
t('startupMessages2', language), <div className="w-3 h-3 rounded-full" style={{ background: '#FFBD2E' }} />
t('startupMessages3', language), <div className="w-3 h-3 rounded-full" style={{ background: '#27C93F' }} />
]} </div>
typingSpeed={70} <span className="text-xs ml-2" style={{ color: '#5E6673' }}>terminal</span>
lineDelay={900} </div>
className="text-sm font-mono"
style={{ {/* Terminal Content */}
color: '#00FF88', <div className="p-6 font-mono text-sm space-y-2">
textShadow: '0 0 8px rgba(0,255,136,0.4)', <div style={{ color: '#5E6673' }}>$ git clone https://github.com/NoFxAiOS/nofx.git</div>
}} <div style={{ color: '#5E6673' }}>$ cd nofx && chmod +x start.sh</div>
/> <div style={{ color: '#5E6673' }}>$ ./start.sh start --build</div>
<div className="pt-2" style={{ color: '#F0B90B' }}>
{t('startupMessages1', language)}
</div>
<div style={{ color: '#0ECB81' }}>
{t('startupMessages2', language)}
</div>
<div style={{ color: '#0ECB81' }}>
{t('startupMessages3', language)}
</div>
<motion.div
className="flex items-center gap-2 pt-2"
animate={{ opacity: [1, 0.5, 1] }}
transition={{ duration: 1.5, repeat: Infinity }}
>
<span style={{ color: '#F0B90B' }}></span>
<span style={{ color: '#EAECEF' }}>_</span>
</motion.div>
</div>
</div> </div>
</div> </motion.div>
</div> </div>
</div> </div>
</AnimatedSection> </section>
) )
} }

View File

@@ -1,7 +1,8 @@
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import AnimatedSection from './AnimatedSection' import { MessageCircle, Heart, Repeat2, ExternalLink } from 'lucide-react'
import { Language } from '../../i18n/translations'
interface CardProps { interface TweetProps {
quote: string quote: string
authorName: string authorName: string
handle: string handle: string
@@ -10,49 +11,94 @@ interface CardProps {
delay: number delay: number
} }
function TestimonialCard({ quote, authorName, delay }: CardProps) { function TweetCard({ quote, authorName, handle, avatarUrl, tweetUrl, delay }: TweetProps) {
return ( return (
<motion.div <motion.a
className="p-6 rounded-xl" href={tweetUrl}
target="_blank"
rel="noopener noreferrer"
className="block p-5 rounded-2xl transition-all duration-300 group"
style={{ style={{
background: 'var(--brand-dark-gray)', background: '#12161C',
border: '1px solid rgba(240, 185, 11, 0.1)', border: '1px solid rgba(255, 255, 255, 0.06)',
}} }}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ delay }} transition={{ delay }}
whileHover={{ scale: 1.05 }} whileHover={{
y: -4,
borderColor: 'rgba(240, 185, 11, 0.3)',
}}
> >
<p className="text-lg mb-4" style={{ color: 'var(--brand-light-gray)' }}> {/* Header */}
"{quote}" <div className="flex items-start justify-between mb-3">
</p> <div className="flex items-center gap-3">
<div className="flex items-center gap-2"> <img
src={avatarUrl}
alt={authorName}
className="w-10 h-10 rounded-full object-cover"
style={{ border: '2px solid rgba(255, 255, 255, 0.1)' }}
/>
<div>
<div className="font-semibold text-sm" style={{ color: '#EAECEF' }}>
{authorName}
</div>
<div className="text-xs" style={{ color: '#5E6673' }}>
{handle}
</div>
</div>
</div>
{/* X Logo */}
<div <div
className="w-8 h-8 rounded-full" className="w-6 h-6 flex items-center justify-center opacity-50 group-hover:opacity-100 transition-opacity"
style={{ background: 'var(--binance-yellow)' }} style={{ color: '#EAECEF' }}
/>
<span
className="text-sm font-semibold"
style={{ color: 'var(--text-secondary)' }}
> >
{authorName} <svg viewBox="0 0 24 24" className="w-4 h-4" fill="currentColor">
</span> <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
</div>
</div> </div>
</motion.div>
{/* Content */}
<p
className="text-sm leading-relaxed mb-4 line-clamp-4"
style={{ color: '#B7BDC6' }}
>
{quote}
</p>
{/* Footer */}
<div className="flex items-center gap-6 pt-3" style={{ borderTop: '1px solid rgba(255, 255, 255, 0.05)' }}>
<div className="flex items-center gap-1.5 text-xs" style={{ color: '#5E6673' }}>
<MessageCircle className="w-3.5 h-3.5" />
<span>Reply</span>
</div>
<div className="flex items-center gap-1.5 text-xs" style={{ color: '#5E6673' }}>
<Repeat2 className="w-3.5 h-3.5" />
<span>Repost</span>
</div>
<div className="flex items-center gap-1.5 text-xs" style={{ color: '#5E6673' }}>
<Heart className="w-3.5 h-3.5" />
<span>Like</span>
</div>
<div className="ml-auto opacity-0 group-hover:opacity-100 transition-opacity">
<ExternalLink className="w-3.5 h-3.5" style={{ color: '#F0B90B' }} />
</div>
</div>
</motion.a>
) )
} }
export default function CommunitySection() { interface CommunitySectionProps {
const staggerContainer = { language?: Language
animate: { transition: { staggerChildren: 0.1 } }, }
}
// 推特内容整合(保持原三列布局,超出自动换行) export default function CommunitySection({ language }: CommunitySectionProps) {
const items: CardProps[] = [ const tweets: TweetProps[] = [
{ {
quote: quote:
'前不久非常火的 AI 量化交易系统 NOF1在 GitHub 上有人将其复刻并开源,这就是 NOFX 项目。基于 DeepSeek、Qwen 等大语言模型,打造的通用架构 AI 交易操作系统,完成了从决策、到交易、再到复盘的闭环。GitHub: https://github.com/NoFxAiOS/nofx', '前不久非常火的 AI 量化交易系统 NOF1在 GitHub 上有人将其复刻并开源,这就是 NOFX 项目。基于 DeepSeek、Qwen 等大语言模型,打造的通用架构 AI 交易操作系统,完成了从决策、到交易、再到复盘的闭环。',
authorName: 'Michael Williams', authorName: 'Michael Williams',
handle: '@MichaelWil93725', handle: '@MichaelWil93725',
avatarUrl: avatarUrl:
@@ -84,20 +130,61 @@ export default function CommunitySection() {
] ]
return ( return (
<AnimatedSection> <section className="py-24 relative" style={{ background: '#0B0E11' }}>
<div className="max-w-7xl mx-auto"> {/* Background Decoration */}
<div
className="absolute right-0 top-1/2 -translate-y-1/2 w-96 h-96 rounded-full blur-3xl opacity-20"
style={{ background: 'radial-gradient(circle, rgba(29, 161, 242, 0.1) 0%, transparent 70%)' }}
/>
<div className="max-w-6xl mx-auto px-4 relative z-10">
{/* Header */}
<motion.div <motion.div
className="grid md:grid-cols-3 gap-6" className="text-center mb-12"
variants={staggerContainer} initial={{ opacity: 0, y: 30 }}
initial="initial" whileInView={{ opacity: 1, y: 0 }}
whileInView="animate"
viewport={{ once: true }} viewport={{ once: true }}
> >
{items.map((item, idx) => ( <h2 className="text-4xl lg:text-5xl font-bold mb-4" style={{ color: '#EAECEF' }}>
<TestimonialCard key={idx} {...item} /> {language === 'zh' ? '社区声音' : 'Community Voices'}
</h2>
<p className="text-lg" style={{ color: '#848E9C' }}>
{language === 'zh' ? '看看大家怎么说' : 'See what others are saying'}
</p>
</motion.div>
{/* Tweet Grid */}
<div className="grid md:grid-cols-3 gap-5">
{tweets.map((tweet, idx) => (
<TweetCard key={idx} {...tweet} />
))} ))}
</div>
{/* CTA */}
<motion.div
className="text-center mt-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
>
<a
href="https://twitter.com/nofx_official"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-6 py-3 rounded-xl font-medium transition-all hover:scale-105"
style={{
background: 'rgba(29, 161, 242, 0.1)',
color: '#1DA1F2',
border: '1px solid rgba(29, 161, 242, 0.3)',
}}
>
<svg viewBox="0 0 24 24" className="w-5 h-5" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
{language === 'zh' ? '关注我们的 X' : 'Follow us on X'}
</a>
</motion.div> </motion.div>
</div> </div>
</AnimatedSection> </section>
) )
} }

View File

@@ -1,7 +1,5 @@
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import AnimatedSection from './AnimatedSection' import { Brain, Swords, BarChart3, Shield, Blocks, LineChart } from 'lucide-react'
import { CryptoFeatureCard } from '../CryptoFeatureCard'
import { Code, Cpu, Lock, Rocket } from 'lucide-react'
import { t, Language } from '../../i18n/translations' import { t, Language } from '../../i18n/translations'
interface FeaturesSectionProps { interface FeaturesSectionProps {
@@ -9,84 +7,199 @@ interface FeaturesSectionProps {
} }
export default function FeaturesSection({ language }: FeaturesSectionProps) { export default function FeaturesSection({ language }: FeaturesSectionProps) {
const features = [
{
icon: Brain,
title: language === 'zh' ? 'AI 策略编排引擎' : 'AI Strategy Orchestration',
desc: language === 'zh'
? '支持 DeepSeek、GPT、Claude、Qwen 等多种大模型,自定义 Prompt 策略AI 自主分析市场并做出交易决策'
: 'Support DeepSeek, GPT, Claude, Qwen and more. Custom prompts, AI autonomously analyzes markets and makes trading decisions',
highlight: true,
badge: language === 'zh' ? '核心能力' : 'Core',
},
{
icon: Swords,
title: language === 'zh' ? '多 AI 竞技场' : 'Multi-AI Arena',
desc: language === 'zh'
? '多个 AI 交易员同台竞技,实时 PnL 排行榜,自动优胜劣汰,让最强策略脱颖而出'
: 'Multiple AI traders compete in real-time, live PnL leaderboard, automatic survival of the fittest',
highlight: true,
badge: language === 'zh' ? '独创' : 'Unique',
},
{
icon: LineChart,
title: language === 'zh' ? '专业量化数据' : 'Pro Quant Data',
desc: language === 'zh'
? '集成 K线、技术指标、市场深度、资金费率、持仓量等专业量化数据为 AI 决策提供全面信息'
: 'Integrated candlesticks, indicators, order book, funding rates, open interest - comprehensive data for AI decisions',
highlight: true,
badge: language === 'zh' ? '专业' : 'Pro',
},
{
icon: Blocks,
title: language === 'zh' ? '多交易所支持' : 'Multi-Exchange Support',
desc: language === 'zh'
? 'Binance、OKX、Bybit、Hyperliquid、Aster DEX一套系统管理多个交易所'
: 'Binance, OKX, Bybit, Hyperliquid, Aster DEX - one system, multiple exchanges',
},
{
icon: BarChart3,
title: language === 'zh' ? '实时可视化看板' : 'Real-time Dashboard',
desc: language === 'zh'
? '交易监控、收益曲线、持仓分析、AI 决策日志,一目了然'
: 'Trade monitoring, PnL curves, position analysis, AI decision logs at a glance',
},
{
icon: Shield,
title: language === 'zh' ? '开源自托管' : 'Open Source & Self-Hosted',
desc: language === 'zh'
? '代码完全开源可审计数据存储在本地API 密钥不经过第三方'
: 'Fully open source, data stored locally, API keys never leave your server',
},
]
return ( return (
<AnimatedSection id="features"> <section className="py-24 relative" style={{ background: '#0B0E11' }}>
<div className="max-w-7xl mx-auto"> {/* Background */}
<div
className="absolute inset-0 opacity-[0.02]"
style={{
backgroundImage: `linear-gradient(#F0B90B 1px, transparent 1px), linear-gradient(90deg, #F0B90B 1px, transparent 1px)`,
backgroundSize: '40px 40px',
}}
/>
<div className="max-w-6xl mx-auto px-4 relative z-10">
{/* Header */}
<motion.div <motion.div
className="text-center mb-16" className="text-center mb-16"
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<motion.div <h2 className="text-4xl lg:text-5xl font-bold mb-4" style={{ color: '#EAECEF' }}>
className="inline-flex items-center gap-2 px-4 py-2 rounded-full mb-6"
style={{
background: 'rgba(240, 185, 11, 0.1)',
border: '1px solid rgba(240, 185, 11, 0.2)',
}}
whileHover={{ scale: 1.05 }}
>
<Rocket
className="w-4 h-4"
style={{ color: 'var(--brand-yellow)' }}
/>
<span
className="text-sm font-semibold"
style={{ color: 'var(--brand-yellow)' }}
>
{t('coreFeatures', language)}
</span>
</motion.div>
<h2
className="text-4xl font-bold mb-4"
style={{ color: 'var(--brand-light-gray)' }}
>
{t('whyChooseNofx', language)} {t('whyChooseNofx', language)}
</h2> </h2>
<p className="text-lg" style={{ color: 'var(--text-secondary)' }}> <p className="text-lg max-w-2xl mx-auto" style={{ color: '#848E9C' }}>
{t('openCommunityDriven', language)} {language === 'zh'
? '不只是交易机器人,而是完整的 AI 交易操作系统'
: 'Not just a trading bot, but a complete AI trading operating system'}
</p> </p>
</motion.div> </motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-7xl mx-auto"> {/* Features Grid */}
<CryptoFeatureCard <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-5">
icon={<Code className="w-8 h-8" />} {features.map((feature, index) => (
title={t('openSourceSelfHosted', language)} <motion.div
description={t('openSourceDesc', language)} key={feature.title}
features={[ initial={{ opacity: 0, y: 20 }}
t('openSourceFeatures1', language), whileInView={{ opacity: 1, y: 0 }}
t('openSourceFeatures2', language), viewport={{ once: true }}
t('openSourceFeatures3', language), transition={{ delay: index * 0.1 }}
t('openSourceFeatures4', language), className={`
]} relative group rounded-2xl p-6 transition-all duration-300
delay={0} ${feature.highlight ? 'md:col-span-1 lg:col-span-1' : ''}
/> `}
<CryptoFeatureCard style={{
icon={<Cpu className="w-8 h-8" />} background: feature.highlight
title={t('multiAgentCompetition', language)} ? 'linear-gradient(135deg, rgba(240, 185, 11, 0.08) 0%, rgba(240, 185, 11, 0.02) 100%)'
description={t('multiAgentDesc', language)} : '#12161C',
features={[ border: feature.highlight
t('multiAgentFeatures1', language), ? '1px solid rgba(240, 185, 11, 0.2)'
t('multiAgentFeatures2', language), : '1px solid rgba(255, 255, 255, 0.06)',
t('multiAgentFeatures3', language), }}
t('multiAgentFeatures4', language), >
]} {/* Badge */}
delay={0.1} {feature.badge && (
/> <div
<CryptoFeatureCard className="absolute top-4 right-4 px-2 py-1 rounded text-xs font-medium"
icon={<Lock className="w-8 h-8" />} style={{
title={t('secureReliableTrading', language)} background: 'rgba(240, 185, 11, 0.15)',
description={t('secureDesc', language)} color: '#F0B90B',
features={[ }}
t('secureFeatures1', language), >
t('secureFeatures2', language), {feature.badge}
t('secureFeatures3', language), </div>
t('secureFeatures4', language), )}
]}
delay={0.2} {/* Icon */}
/> <motion.div
className="w-12 h-12 rounded-xl flex items-center justify-center mb-4"
style={{
background: feature.highlight
? 'rgba(240, 185, 11, 0.15)'
: 'rgba(240, 185, 11, 0.1)',
border: '1px solid rgba(240, 185, 11, 0.2)',
}}
whileHover={{ scale: 1.1, rotate: 5 }}
>
<feature.icon
className="w-6 h-6"
style={{ color: '#F0B90B' }}
/>
</motion.div>
{/* Text */}
<h3
className="text-xl font-bold mb-3"
style={{ color: '#EAECEF' }}
>
{feature.title}
</h3>
<p
className="text-sm leading-relaxed"
style={{ color: '#848E9C' }}
>
{feature.desc}
</p>
{/* Hover Glow */}
<div
className="absolute -bottom-10 -right-10 w-32 h-32 rounded-full blur-3xl opacity-0 group-hover:opacity-30 transition-opacity duration-500"
style={{ background: '#F0B90B' }}
/>
</motion.div>
))}
</div> </div>
{/* Bottom Stats */}
<motion.div
className="mt-16 grid grid-cols-2 md:grid-cols-4 gap-6"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
>
{[
{ value: '10+', label: language === 'zh' ? 'AI 模型支持' : 'AI Models' },
{ value: '5+', label: language === 'zh' ? '交易所集成' : 'Exchanges' },
{ value: '24/7', label: language === 'zh' ? '自动交易' : 'Auto Trading' },
{ value: '100%', label: language === 'zh' ? '开源免费' : 'Open Source' },
].map((stat) => (
<div
key={stat.label}
className="text-center p-4 rounded-xl"
style={{
background: 'rgba(255, 255, 255, 0.02)',
border: '1px solid rgba(255, 255, 255, 0.05)',
}}
>
<div
className="text-2xl font-bold mb-1"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{stat.value}
</div>
<div className="text-xs" style={{ color: '#5E6673' }}>
{stat.label}
</div>
</div>
))}
</motion.div>
</div> </div>
</AnimatedSection> </section>
) )
} }

View File

@@ -1,180 +1,169 @@
import { Github, Send, ExternalLink } from 'lucide-react'
import { t, Language } from '../../i18n/translations' import { t, Language } from '../../i18n/translations'
import { OFFICIAL_LINKS } from '../../constants/branding'
interface FooterSectionProps { interface FooterSectionProps {
language: Language language: Language
} }
export default function FooterSection({ language }: FooterSectionProps) { export default function FooterSection({ language }: FooterSectionProps) {
const links = {
social: [
{ name: 'GitHub', href: OFFICIAL_LINKS.github, icon: Github },
{
name: 'X (Twitter)',
href: OFFICIAL_LINKS.twitter,
icon: () => (
<svg viewBox="0 0 24 24" className="w-4 h-4" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
),
},
{ name: 'Telegram', href: OFFICIAL_LINKS.telegram, icon: Send },
],
resources: [
{
name: language === 'zh' ? '文档' : 'Documentation',
href: 'https://github.com/NoFxAiOS/nofx/blob/main/README.md',
},
{ name: 'Issues', href: 'https://github.com/NoFxAiOS/nofx/issues' },
{ name: 'Pull Requests', href: 'https://github.com/NoFxAiOS/nofx/pulls' },
],
supporters: [
{ name: 'Aster DEX', href: 'https://www.asterdex.com/en/referral/fdfc0e' },
{ name: 'Binance', href: 'https://www.maxweb.red/join?ref=NOFXAI' },
{ name: 'Hyperliquid', href: 'https://hyperliquid.xyz/' },
{
name: 'Amber.ac',
href: 'https://amber.ac/',
badge: language === 'zh' ? '战略投资' : 'Strategic',
},
],
}
return ( return (
<footer <footer style={{ background: '#0B0E11', borderTop: '1px solid rgba(255, 255, 255, 0.06)' }}>
style={{ <div className="max-w-6xl mx-auto px-4 py-12">
borderTop: '1px solid var(--panel-border)', {/* Top Section */}
background: 'var(--brand-dark-gray)', <div className="grid grid-cols-1 md:grid-cols-4 gap-10 mb-12">
}} {/* Brand */}
> <div className="md:col-span-1">
<div className="max-w-[1200px] mx-auto px-6 py-10"> <div className="flex items-center gap-3 mb-4">
{/* Brand */} <img src="/icons/nofx.svg" alt="NOFX Logo" className="w-8 h-8" />
<div className="flex items-center gap-3 mb-8"> <span className="text-xl font-bold" style={{ color: '#EAECEF' }}>
<img src="/icons/nofx.svg" alt="NOFX Logo" className="w-8 h-8" /> NOFX
<div> </span>
<div className="text-lg font-bold" style={{ color: '#EAECEF' }}>
NOFX
</div> </div>
<div className="text-xs" style={{ color: '#848E9C' }}> <p className="text-sm mb-6" style={{ color: '#5E6673' }}>
{t('futureStandardAI', language)} {t('futureStandardAI', language)}
</p>
{/* Social Icons */}
<div className="flex items-center gap-3">
{links.social.map((link) => (
<a
key={link.name}
href={link.href}
target="_blank"
rel="noopener noreferrer"
className="w-9 h-9 rounded-lg flex items-center justify-center transition-all hover:scale-110"
style={{
background: 'rgba(255, 255, 255, 0.05)',
color: '#848E9C',
}}
title={link.name}
>
<link.icon className="w-4 h-4" />
</a>
))}
</div> </div>
</div> </div>
</div>
{/* Multi-link columns */} {/* Links */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-3 gap-8">
<div> <div>
<h3 <h4 className="text-sm font-semibold mb-4" style={{ color: '#EAECEF' }}>
className="text-sm font-semibold mb-3"
style={{ color: '#EAECEF' }}
>
{t('links', language)} {t('links', language)}
</h3> </h4>
<ul className="space-y-2 text-sm" style={{ color: '#848E9C' }}> <ul className="space-y-3">
<li> {links.social.map((link) => (
<a <li key={link.name}>
className="hover:text-[#F0B90B]" <a
href="https://github.com/tinkle-community/nofx" href={link.href}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> className="text-sm transition-colors hover:text-[#F0B90B]"
GitHub style={{ color: '#5E6673' }}
</a> >
</li> {link.name}
<li> </a>
<a </li>
className="hover:text-[#F0B90B]" ))}
href="https://t.me/nofx_dev_community"
target="_blank"
rel="noopener noreferrer"
>
Telegram
</a>
</li>
<li>
<a
className="hover:text-[#F0B90B]"
href="https://x.com/nofx_official"
target="_blank"
rel="noopener noreferrer"
>
X (Twitter)
</a>
</li>
</ul> </ul>
</div> </div>
{/* Resources */}
<div> <div>
<h3 <h4 className="text-sm font-semibold mb-4" style={{ color: '#EAECEF' }}>
className="text-sm font-semibold mb-3"
style={{ color: '#EAECEF' }}
>
{t('resources', language)} {t('resources', language)}
</h3> </h4>
<ul className="space-y-2 text-sm" style={{ color: '#848E9C' }}> <ul className="space-y-3">
<li> {links.resources.map((link) => (
<a <li key={link.name}>
className="hover:text-[#F0B90B]" <a
href="https://github.com/tinkle-community/nofx/blob/main/README.md" href={link.href}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> className="text-sm transition-colors hover:text-[#F0B90B] inline-flex items-center gap-1"
{t('documentation', language)} style={{ color: '#5E6673' }}
</a> >
</li> {link.name}
<li> <ExternalLink className="w-3 h-3 opacity-50" />
<a </a>
className="hover:text-[#F0B90B]" </li>
href="https://github.com/tinkle-community/nofx/issues" ))}
target="_blank"
rel="noopener noreferrer"
>
Issues
</a>
</li>
<li>
<a
className="hover:text-[#F0B90B]"
href="https://github.com/tinkle-community/nofx/pulls"
target="_blank"
rel="noopener noreferrer"
>
Pull Requests
</a>
</li>
</ul> </ul>
</div> </div>
{/* Supporters */}
<div> <div>
<h3 <h4 className="text-sm font-semibold mb-4" style={{ color: '#EAECEF' }}>
className="text-sm font-semibold mb-3"
style={{ color: '#EAECEF' }}
>
{t('supporters', language)} {t('supporters', language)}
</h3> </h4>
<ul className="space-y-2 text-sm" style={{ color: '#848E9C' }}> <ul className="space-y-3">
<li> {links.supporters.map((link) => (
<a <li key={link.name}>
className="hover:text-[#F0B90B]" <a
href="https://www.asterdex.com/en/referral/fdfc0e" href={link.href}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> className="text-sm transition-colors hover:text-[#F0B90B] inline-flex items-center gap-2"
Aster DEX style={{ color: '#5E6673' }}
</a> >
</li> {link.name}
<li> {link.badge && (
<a <span
className="hover:text-[#F0B90B]" className="text-xs px-1.5 py-0.5 rounded"
href="https://www.maxweb.red/join?ref=NOFXAI" style={{
target="_blank" background: 'rgba(240, 185, 11, 0.1)',
rel="noopener noreferrer" color: '#F0B90B',
> }}
Binance >
</a> {link.badge}
</li> </span>
<li> )}
<a </a>
className="hover:text-[#F0B90B]" </li>
href="https://hyperliquid.xyz/" ))}
target="_blank"
rel="noopener noreferrer"
>
Hyperliquid
</a>
</li>
<li>
<a
className="hover:text-[#F0B90B]"
href="https://amber.ac/"
target="_blank"
rel="noopener noreferrer"
>
Amber.ac{' '}
<span className="opacity-70">
{t('strategicInvestment', language)}
</span>
</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
{/* Bottom note (kept subtle) */} {/* Bottom Section */}
<div <div
className="pt-6 mt-8 text-center text-xs" className="pt-6 text-center text-xs"
style={{ style={{ color: '#5E6673', borderTop: '1px solid rgba(255, 255, 255, 0.06)' }}
color: 'var(--text-tertiary)',
borderTop: '1px solid var(--panel-border)',
}}
> >
<p>{t('footerTitle', language)}</p> <p className="mb-2">{t('footerTitle', language)}</p>
<p className="mt-1">{t('footerWarning', language)}</p> <p style={{ color: '#3C4249' }}>{t('footerWarning', language)}</p>
</div> </div>
</div> </div>
</footer> </footer>

View File

@@ -1,214 +1,252 @@
import { motion, useScroll, useTransform, useAnimation } from 'framer-motion' import { motion } from 'framer-motion'
import { Sparkles } from 'lucide-react' import { ArrowRight, Play, Github, Zap } from 'lucide-react'
import { t, Language } from '../../i18n/translations' import { t, Language } from '../../i18n/translations'
import { useGitHubStats } from '../../hooks/useGitHubStats' import { useGitHubStats } from '../../hooks/useGitHubStats'
import { useCounterAnimation } from '../../hooks/useCounterAnimation' import { useCounterAnimation } from '../../hooks/useCounterAnimation'
import { OFFICIAL_LINKS } from '../../constants/branding'
interface HeroSectionProps { interface HeroSectionProps {
language: Language language: Language
} }
export default function HeroSection({ language }: HeroSectionProps) { export default function HeroSection({ language }: HeroSectionProps) {
const { scrollYProgress } = useScroll()
const opacity = useTransform(scrollYProgress, [0, 0.2], [1, 0])
const scale = useTransform(scrollYProgress, [0, 0.2], [1, 0.8])
const handControls = useAnimation()
const { stars, daysOld, isLoading } = useGitHubStats('NoFxAiOS', 'nofx') const { stars, daysOld, isLoading } = useGitHubStats('NoFxAiOS', 'nofx')
// 动画数字 - 仅对 stars 添加动画
const animatedStars = useCounterAnimation({ const animatedStars = useCounterAnimation({
start: 0, start: 0,
end: stars, end: stars,
duration: 2000, duration: 2000,
}) })
const fadeInUp = {
initial: { opacity: 0, y: 60 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.6, ease: [0.6, -0.05, 0.01, 0.99] },
}
const staggerContainer = {
animate: { transition: { staggerChildren: 0.1 } },
}
return ( return (
<section className="relative pt-32 pb-20 px-4"> <section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-16">
<div className="max-w-7xl mx-auto"> {/* Animated Background */}
<div className="grid lg:grid-cols-2 gap-12 items-center"> <div className="absolute inset-0 overflow-hidden">
{/* Left Content */} {/* Grid Pattern */}
<motion.div <div
className="space-y-6 relative z-10" className="absolute inset-0 opacity-[0.03]"
style={{ opacity, scale }} style={{
initial="initial" backgroundImage: `linear-gradient(#F0B90B 1px, transparent 1px), linear-gradient(90deg, #F0B90B 1px, transparent 1px)`,
animate="animate" backgroundSize: '60px 60px',
variants={staggerContainer} }}
> />
<motion.div variants={fadeInUp}> {/* Radial Gradient */}
<motion.div <div
className="inline-flex items-center gap-2 px-4 py-2 rounded-full mb-6" className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] rounded-full"
style={{ style={{
background: 'rgba(240, 185, 11, 0.1)', background: 'radial-gradient(circle, rgba(240, 185, 11, 0.08) 0%, transparent 70%)',
border: '1px solid rgba(240, 185, 11, 0.2)', }}
}} />
whileHover={{ {/* Floating Orbs */}
scale: 1.05, <motion.div
boxShadow: '0 0 20px rgba(240, 185, 11, 0.2)', className="absolute top-20 right-20 w-32 h-32 rounded-full blur-3xl"
}} style={{ background: 'rgba(240, 185, 11, 0.15)' }}
> animate={{
<Sparkles y: [0, 30, 0],
className="w-4 h-4" scale: [1, 1.1, 1],
style={{ color: 'var(--brand-yellow)' }} }}
/> transition={{ duration: 8, repeat: Infinity, ease: 'easeInOut' }}
<span />
className="text-sm font-semibold" <motion.div
style={{ color: 'var(--brand-yellow)' }} className="absolute bottom-40 left-20 w-48 h-48 rounded-full blur-3xl"
> style={{ background: 'rgba(240, 185, 11, 0.1)' }}
{isLoading ? ( animate={{
t('githubStarsInDays', language) y: [0, -40, 0],
) : language === 'zh' ? ( scale: [1, 1.2, 1],
<> }}
{daysOld} {' '} transition={{ duration: 10, repeat: Infinity, ease: 'easeInOut' }}
<span className="inline-block tabular-nums"> />
{(animatedStars / 1000).toFixed(1)}
</span>
K+ GitHub Stars
</>
) : (
<>
<span className="inline-block tabular-nums">
{(animatedStars / 1000).toFixed(1)}
</span>
K+ GitHub Stars in {daysOld} days
</>
)}
</span>
</motion.div>
</motion.div>
<h1
className="text-5xl lg:text-7xl font-bold leading-tight"
style={{ color: 'var(--brand-light-gray)' }}
>
{t('heroTitle1', language)}
<br />
<span style={{ color: 'var(--brand-yellow)' }}>
{t('heroTitle2', language)}
</span>
</h1>
<motion.p
className="text-xl leading-relaxed"
style={{ color: 'var(--text-secondary)' }}
variants={fadeInUp}
>
{t('heroDescription', language)}
</motion.p>
<div className="flex items-center gap-3 flex-wrap">
<motion.a
href="https://github.com/tinkle-community/nofx"
target="_blank"
rel="noopener noreferrer"
whileHover={{ scale: 1.05 }}
transition={{ type: 'spring', stiffness: 400 }}
>
<img
src="https://img.shields.io/github/stars/tinkle-community/nofx?style=for-the-badge&logo=github&logoColor=white&color=F0B90B&labelColor=0A0A0A"
alt="GitHub Stars"
className="h-7"
/>
</motion.a>
<motion.a
href="https://github.com/tinkle-community/nofx/network/members"
target="_blank"
rel="noopener noreferrer"
whileHover={{ scale: 1.05 }}
transition={{ type: 'spring', stiffness: 400 }}
>
<img
src="https://img.shields.io/github/forks/tinkle-community/nofx?style=for-the-badge&logo=github&logoColor=white&color=F0B90B&labelColor=0A0A0A"
alt="GitHub Forks"
className="h-7"
/>
</motion.a>
<motion.a
href="https://github.com/tinkle-community/nofx/graphs/contributors"
target="_blank"
rel="noopener noreferrer"
whileHover={{ scale: 1.05 }}
transition={{ type: 'spring', stiffness: 400 }}
>
<img
src="https://img.shields.io/github/contributors/tinkle-community/nofx?style=for-the-badge&logo=github&logoColor=white&color=F0B90B&labelColor=0A0A0A"
alt="GitHub Contributors"
className="h-7"
/>
</motion.a>
</div>
<motion.p
className="text-xs pt-4"
style={{ color: 'var(--text-tertiary)' }}
variants={fadeInUp}
>
{t('poweredBy', language)}
</motion.p>
</motion.div>
{/* Right Visual - Interactive Robot */}
<div
className="relative w-full cursor-pointer"
onMouseEnter={() => {
handControls.start({
y: [-8, 8, -8],
rotate: [-3, 3, -3],
x: [-2, 2, -2],
transition: {
duration: 2.5,
repeat: Infinity,
ease: 'easeInOut',
times: [0, 0.5, 1],
},
})
}}
onMouseLeave={() => {
handControls.start({
y: 0,
rotate: 0,
x: 0,
transition: {
duration: 0.6,
ease: 'easeOut',
},
})
}}
>
{/* Background Layer */}
<motion.img
src="/images/hand-bg.png"
alt="NOFX Platform Background"
className="w-full opacity-90"
style={{ opacity, scale }}
whileHover={{ scale: 1.02 }}
transition={{ type: 'spring', stiffness: 300 }}
/>
{/* Hand Layer - Animated */}
<motion.img
src="/images/hand.png"
alt="Robot Hand"
className="absolute top-0 left-0 w-full"
style={{ opacity }}
animate={handControls}
initial={{ y: 0, rotate: 0, x: 0 }}
whileHover={{
scale: 1.05,
transition: { type: 'spring', stiffness: 400 },
}}
/>
</div>
</div>
</div> </div>
<div className="relative z-10 max-w-6xl mx-auto px-4 text-center">
{/* Badge */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="inline-flex items-center gap-2 px-4 py-2 rounded-full mb-8"
style={{
background: 'rgba(240, 185, 11, 0.1)',
border: '1px solid rgba(240, 185, 11, 0.3)',
}}
>
<Zap className="w-4 h-4" style={{ color: '#F0B90B' }} />
<span className="text-sm font-medium" style={{ color: '#F0B90B' }}>
{isLoading ? (
t('githubStarsInDays', language)
) : language === 'zh' ? (
<>
{daysOld} {' '}
<span className="font-bold tabular-nums">
{(animatedStars / 1000).toFixed(1)}K+
</span>{' '}
GitHub Stars
</>
) : (
<>
<span className="font-bold tabular-nums">
{(animatedStars / 1000).toFixed(1)}K+
</span>{' '}
GitHub Stars in {daysOld} days
</>
)}
</span>
</motion.div>
{/* Main Title */}
<motion.h1
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-5xl sm:text-6xl lg:text-7xl font-bold mb-6 leading-tight"
>
<span style={{ color: '#EAECEF' }}>{t('heroTitle1', language)}</span>
<br />
<span
className="relative inline-block"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{t('heroTitle2', language)}
<motion.span
className="absolute -bottom-2 left-0 h-1 rounded-full"
style={{ background: 'linear-gradient(90deg, #F0B90B, #FCD535)' }}
initial={{ width: 0 }}
animate={{ width: '100%' }}
transition={{ duration: 0.8, delay: 0.8 }}
/>
</span>
</motion.h1>
{/* Description */}
<motion.p
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-lg sm:text-xl max-w-3xl mx-auto mb-10 leading-relaxed"
style={{ color: '#848E9C' }}
>
{t('heroDescription', language)}
</motion.p>
{/* CTA Buttons */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="flex flex-col sm:flex-row items-center justify-center gap-4 mb-12"
>
<motion.a
href="/competition"
className="group flex items-center gap-3 px-8 py-4 rounded-xl font-bold text-lg transition-all"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
color: '#0B0E11',
boxShadow: '0 4px 24px rgba(240, 185, 11, 0.3)',
}}
whileHover={{
scale: 1.02,
boxShadow: '0 8px 32px rgba(240, 185, 11, 0.4)',
}}
whileTap={{ scale: 0.98 }}
>
<Play className="w-5 h-5" />
{t('liveCompetition', language) || 'Live Competition'}
<ArrowRight className="w-5 h-5 transition-transform group-hover:translate-x-1" />
</motion.a>
<motion.a
href={OFFICIAL_LINKS.github}
target="_blank"
rel="noopener noreferrer"
className="group flex items-center gap-3 px-8 py-4 rounded-xl font-bold text-lg transition-all"
style={{
background: 'rgba(255, 255, 255, 0.05)',
color: '#EAECEF',
border: '1px solid rgba(255, 255, 255, 0.1)',
}}
whileHover={{
scale: 1.02,
background: 'rgba(255, 255, 255, 0.08)',
borderColor: 'rgba(240, 185, 11, 0.3)',
}}
whileTap={{ scale: 0.98 }}
>
<Github className="w-5 h-5" />
{t('viewSourceCode', language)}
</motion.a>
</motion.div>
{/* Stats Row */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="flex flex-wrap items-center justify-center gap-8 sm:gap-12"
>
{[
{ label: 'GitHub Stars', value: `${(stars / 1000).toFixed(1)}K+` },
{ label: language === 'zh' ? '支持交易所' : 'Exchanges', value: '5+' },
{ label: language === 'zh' ? 'AI 模型' : 'AI Models', value: '10+' },
{ label: language === 'zh' ? '开源免费' : 'Open Source', value: '100%' },
].map((stat, index) => (
<motion.div
key={stat.label}
className="text-center"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 + index * 0.1 }}
>
<div
className="text-3xl sm:text-4xl font-bold mb-1"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
{stat.value}
</div>
<div className="text-sm" style={{ color: '#5E6673' }}>
{stat.label}
</div>
</motion.div>
))}
</motion.div>
{/* Powered By */}
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6, delay: 0.8 }}
className="mt-16 text-xs"
style={{ color: '#5E6673' }}
>
{t('poweredBy', language)}
</motion.p>
</div>
{/* Scroll Indicator */}
<motion.div
className="absolute bottom-8 left-1/2 -translate-x-1/2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
>
<motion.div
className="w-6 h-10 rounded-full flex justify-center pt-2"
style={{ border: '2px solid rgba(240, 185, 11, 0.3)' }}
animate={{ y: [0, 8, 0] }}
transition={{ duration: 2, repeat: Infinity }}
>
<motion.div
className="w-1.5 h-3 rounded-full"
style={{ background: '#F0B90B' }}
/>
</motion.div>
</motion.div>
</section> </section>
) )
} }

View File

@@ -1,140 +1,167 @@
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import AnimatedSection from './AnimatedSection' import { Download, Rocket, TrendingUp, AlertTriangle } from 'lucide-react'
import { t, Language } from '../../i18n/translations' import { t, Language } from '../../i18n/translations'
function StepCard({ number, title, description, delay }: any) {
return (
<motion.div
className="flex gap-6 items-start"
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay }}
whileHover={{ x: 10 }}
>
<motion.div
className="flex-shrink-0 w-14 h-14 rounded-full flex items-center justify-center font-bold text-2xl"
style={{
background: 'var(--binance-yellow)',
color: 'var(--brand-black)',
}}
whileHover={{ scale: 1.2, rotate: 360 }}
transition={{ type: 'spring', stiffness: 260, damping: 20 }}
>
{number}
</motion.div>
<div>
<h3
className="text-2xl font-semibold mb-2"
style={{ color: 'var(--brand-light-gray)' }}
>
{title}
</h3>
<p
className="text-lg leading-relaxed"
style={{ color: 'var(--text-secondary)' }}
>
{description}
</p>
</div>
</motion.div>
)
}
interface HowItWorksSectionProps { interface HowItWorksSectionProps {
language: Language language: Language
} }
export default function HowItWorksSection({ export default function HowItWorksSection({ language }: HowItWorksSectionProps) {
language, const steps = [
}: HowItWorksSectionProps) { {
icon: Download,
number: '01',
title: language === 'zh' ? '克隆项目' : 'Clone Project',
desc: language === 'zh'
? 'git clone 项目到本地'
: 'git clone the project locally',
code: 'git clone https://github.com/NoFxAiOS/nofx.git',
},
{
icon: Rocket,
number: '02',
title: language === 'zh' ? '启动服务' : 'Start Service',
desc: language === 'zh'
? 'Docker 一键启动所有服务'
: 'Docker one-click start all services',
code: './start.sh start --build',
},
{
icon: TrendingUp,
number: '03',
title: language === 'zh' ? '开始交易' : 'Start Trading',
desc: language === 'zh'
? '创建交易员,让 AI 开始工作'
: 'Create trader, let AI do the work',
code: 'http://localhost:3000',
},
]
return ( return (
<AnimatedSection id="how-it-works" backgroundColor="var(--brand-dark-gray)"> <section className="py-24 relative overflow-hidden" style={{ background: '#0D1117' }}>
<div className="max-w-7xl mx-auto"> {/* Background Decoration */}
<div
className="absolute left-0 top-1/2 -translate-y-1/2 w-96 h-96 rounded-full blur-3xl opacity-20"
style={{ background: 'radial-gradient(circle, rgba(240, 185, 11, 0.15) 0%, transparent 70%)' }}
/>
<div className="max-w-6xl mx-auto px-4 relative z-10">
{/* Header */}
<motion.div <motion.div
className="text-center mb-16" className="text-center mb-16"
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<h2 <h2 className="text-4xl lg:text-5xl font-bold mb-4" style={{ color: '#EAECEF' }}>
className="text-4xl font-bold mb-4"
style={{ color: 'var(--brand-light-gray)' }}
>
{t('howToStart', language)} {t('howToStart', language)}
</h2> </h2>
<p className="text-lg" style={{ color: 'var(--text-secondary)' }}> <p className="text-lg" style={{ color: '#848E9C' }}>
{t('fourSimpleSteps', language)} {t('fourSimpleSteps', language)}
</p> </p>
</motion.div> </motion.div>
<div className="space-y-8"> {/* Steps Timeline */}
{[ <div className="relative">
{ {/* Connecting Line */}
number: 1, <div
title: t('step1Title', language), className="absolute left-[39px] top-0 bottom-0 w-px hidden lg:block"
description: t('step1Desc', language), style={{ background: 'linear-gradient(to bottom, transparent, rgba(240, 185, 11, 0.3), transparent)' }}
}, />
{
number: 2, <div className="space-y-6">
title: t('step2Title', language), {steps.map((step, index) => (
description: t('step2Desc', language), <motion.div
}, key={step.number}
{ initial={{ opacity: 0, x: -30 }}
number: 3, whileInView={{ opacity: 1, x: 0 }}
title: t('step3Title', language), viewport={{ once: true }}
description: t('step3Desc', language), transition={{ delay: index * 0.15 }}
}, className="relative"
{ >
number: 4, <div
title: t('step4Title', language), className="flex flex-col lg:flex-row items-start gap-6 p-6 rounded-2xl transition-all duration-300 hover:translate-x-2"
description: t('step4Desc', language), style={{
}, background: 'rgba(255, 255, 255, 0.02)',
].map((step, index) => ( border: '1px solid rgba(255, 255, 255, 0.05)',
<StepCard key={step.number} {...step} delay={index * 0.1} /> }}
))} >
{/* Number Circle */}
<div className="flex-shrink-0 relative z-10">
<motion.div
className="w-20 h-20 rounded-2xl flex items-center justify-center"
style={{
background: 'linear-gradient(135deg, rgba(240, 185, 11, 0.2) 0%, rgba(240, 185, 11, 0.05) 100%)',
border: '1px solid rgba(240, 185, 11, 0.3)',
}}
whileHover={{ scale: 1.1 }}
>
<step.icon className="w-8 h-8" style={{ color: '#F0B90B' }} />
</motion.div>
</div>
{/* Content */}
<div className="flex-grow">
<div className="flex items-center gap-3 mb-2">
<span
className="text-sm font-mono font-bold"
style={{ color: '#F0B90B' }}
>
{step.number}
</span>
<h3 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
{step.title}
</h3>
</div>
<p className="mb-4" style={{ color: '#848E9C' }}>
{step.desc}
</p>
{/* Code Block */}
<div
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg font-mono text-sm"
style={{
background: 'rgba(0, 0, 0, 0.3)',
border: '1px solid rgba(255, 255, 255, 0.06)',
}}
>
<span style={{ color: '#5E6673' }}>$</span>
<span style={{ color: '#EAECEF' }}>{step.code}</span>
</div>
</div>
</div>
</motion.div>
))}
</div>
</div> </div>
{/* Risk Warning */}
<motion.div <motion.div
className="mt-12 p-6 rounded-xl flex items-start gap-4" className="mt-12 p-6 rounded-2xl flex items-start gap-4"
style={{ style={{
background: 'rgba(246, 70, 93, 0.1)', background: 'rgba(240, 185, 11, 0.05)',
border: '1px solid rgba(246, 70, 93, 0.3)', border: '1px solid rgba(240, 185, 11, 0.15)',
}} }}
initial={{ opacity: 0, scale: 0.9 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, scale: 1 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
whileHover={{ scale: 1.02 }}
> >
<div <div
className="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0" className="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0"
style={{ background: 'rgba(246, 70, 93, 0.2)', color: '#F6465D' }} style={{ background: 'rgba(240, 185, 11, 0.1)' }}
> >
<svg <AlertTriangle className="w-6 h-6" style={{ color: '#F0B90B' }} />
xmlns="http://www.w3.org/2000/svg"
className="w-6 h-6"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z" />
<line x1="12" x2="12" y1="9" y2="13" />
<line x1="12" x2="12.01" y1="17" y2="17" />
</svg>
</div> </div>
<div> <div>
<div className="font-semibold mb-2" style={{ color: '#F6465D' }}> <div className="font-semibold mb-2" style={{ color: '#F0B90B' }}>
{t('importantRiskWarning', language)} {t('importantRiskWarning', language)}
</div> </div>
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}> <p className="text-sm leading-relaxed" style={{ color: '#5E6673' }}>
{t('riskWarningText', language)} {t('riskWarningText', language)}
</p> </p>
</div> </div>
</motion.div> </motion.div>
</div> </div>
</AnimatedSection> </section>
) )
} }

View File

@@ -11,7 +11,7 @@ import {
WebCryptoEnvironmentCheck, WebCryptoEnvironmentCheck,
type WebCryptoCheckStatus, type WebCryptoCheckStatus,
} from '../WebCryptoEnvironmentCheck' } from '../WebCryptoEnvironmentCheck'
import { BookOpen, Trash2, HelpCircle } from 'lucide-react' import { BookOpen, Trash2, HelpCircle, ExternalLink, UserPlus } from 'lucide-react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { Tooltip } from './Tooltip' import { Tooltip } from './Tooltip'
import { getShortName } from './utils' import { getShortName } from './utils'
@@ -92,6 +92,16 @@ export function ExchangeConfigModal({
(e) => e.id === selectedExchangeId (e) => e.id === selectedExchangeId
) )
// 交易所注册链接配置
const exchangeRegistrationLinks: Record<string, { url: string; hasReferral?: boolean }> = {
binance: { url: 'https://www.binance.com/join?ref=NOFXENG', hasReferral: true },
okx: { url: 'https://www.okx.com/join/1865360', hasReferral: true },
bybit: { url: 'https://partner.bybit.com/b/83856', hasReferral: true },
hyperliquid: { url: 'https://app.hyperliquid.xyz/join/AITRADING', hasReferral: true },
aster: { url: 'https://www.asterdex.com/en/referral/fdfc0e', hasReferral: true },
lighter: { url: 'https://lighter.xyz', hasReferral: false },
}
// 如果是编辑现有交易所,初始化表单数据 // 如果是编辑现有交易所,初始化表单数据
useEffect(() => { useEffect(() => {
if (editingExchangeId && selectedExchange) { if (editingExchangeId && selectedExchange) {
@@ -411,6 +421,34 @@ export function ExchangeConfigModal({
</div> </div>
</div> </div>
</div> </div>
{/* 注册链接 */}
<a
href={exchangeRegistrationLinks[selectedExchange.id]?.url || '#'}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between p-3 rounded-lg mt-3 transition-all hover:scale-[1.02]"
style={{
background: 'rgba(240, 185, 11, 0.08)',
border: '1px solid rgba(240, 185, 11, 0.2)',
}}
>
<div className="flex items-center gap-2">
<UserPlus className="w-4 h-4" style={{ color: '#F0B90B' }} />
<span className="text-sm" style={{ color: '#EAECEF' }}>
{language === 'zh' ? '还没有交易所账号?点击注册' : "No exchange account? Register here"}
</span>
{exchangeRegistrationLinks[selectedExchange.id]?.hasReferral && (
<span
className="text-xs px-1.5 py-0.5 rounded"
style={{ background: 'rgba(14, 203, 129, 0.2)', color: '#0ECB81' }}
>
{language === 'zh' ? '折扣优惠' : 'Discount'}
</span>
)}
</div>
<ExternalLink className="w-4 h-4" style={{ color: '#848E9C' }} />
</a>
</div> </div>
)} )}

View File

@@ -1,18 +1,18 @@
import { useState } from 'react' import { useState } from 'react'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { ArrowRight } from 'lucide-react' import { ArrowRight, Github } from 'lucide-react'
import HeaderBar from '../components/HeaderBar' import HeaderBar from '../components/HeaderBar'
import HeroSection from '../components/landing/HeroSection' import HeroSection from '../components/landing/HeroSection'
import AboutSection from '../components/landing/AboutSection' import AboutSection from '../components/landing/AboutSection'
import FeaturesSection from '../components/landing/FeaturesSection' import FeaturesSection from '../components/landing/FeaturesSection'
import HowItWorksSection from '../components/landing/HowItWorksSection' import HowItWorksSection from '../components/landing/HowItWorksSection'
import CommunitySection from '../components/landing/CommunitySection' import CommunitySection from '../components/landing/CommunitySection'
import AnimatedSection from '../components/landing/AnimatedSection'
import LoginModal from '../components/landing/LoginModal' import LoginModal from '../components/landing/LoginModal'
import FooterSection from '../components/landing/FooterSection' import FooterSection from '../components/landing/FooterSection'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { useLanguage } from '../contexts/LanguageContext' import { useLanguage } from '../contexts/LanguageContext'
import { t } from '../i18n/translations' import { t } from '../i18n/translations'
import { OFFICIAL_LINKS } from '../constants/branding'
export function LandingPage() { export function LandingPage() {
const [showLoginModal, setShowLoginModal] = useState(false) const [showLoginModal, setShowLoginModal] = useState(false)
@@ -20,7 +20,6 @@ export function LandingPage() {
const { language, setLanguage } = useLanguage() const { language, setLanguage } = useLanguage()
const isLoggedIn = !!user const isLoggedIn = !!user
console.log('LandingPage - user:', user, 'isLoggedIn:', isLoggedIn)
return ( return (
<> <>
<HeaderBar <HeaderBar
@@ -32,7 +31,6 @@ export function LandingPage() {
user={user} user={user}
onLogout={logout} onLogout={logout}
onPageChange={(page) => { onPageChange={(page) => {
console.log('LandingPage onPageChange called with:', page)
if (page === 'competition') { if (page === 'competition') {
window.location.href = '/competition' window.location.href = '/competition'
} else if (page === 'traders') { } else if (page === 'traders') {
@@ -43,24 +41,30 @@ export function LandingPage() {
}} }}
/> />
<div <div
className="min-h-screen px-4 sm:px-6 lg:px-8" className="min-h-screen"
style={{ style={{
background: 'var(--brand-black)', background: '#0B0E11',
color: 'var(--brand-light-gray)', color: '#EAECEF',
}} }}
> >
<HeroSection language={language} /> <HeroSection language={language} />
<AboutSection language={language} /> <AboutSection language={language} />
<FeaturesSection language={language} /> <FeaturesSection language={language} />
<HowItWorksSection language={language} /> <HowItWorksSection language={language} />
<CommunitySection /> <CommunitySection language={language} />
{/* CTA */} {/* Final CTA Section */}
<AnimatedSection backgroundColor="var(--panel-bg)"> <section className="py-24 relative overflow-hidden" style={{ background: '#0D1117' }}>
<div className="max-w-4xl mx-auto text-center"> {/* Background Glow */}
<div
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full blur-3xl opacity-30"
style={{ background: 'radial-gradient(circle, rgba(240, 185, 11, 0.15) 0%, transparent 70%)' }}
/>
<div className="max-w-4xl mx-auto px-4 text-center relative z-10">
<motion.h2 <motion.h2
className="text-5xl font-bold mb-6" className="text-4xl lg:text-5xl font-bold mb-6"
style={{ color: 'var(--brand-light-gray)' }} style={{ color: '#EAECEF' }}
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
@@ -68,8 +72,8 @@ export function LandingPage() {
{t('readyToDefine', language)} {t('readyToDefine', language)}
</motion.h2> </motion.h2>
<motion.p <motion.p
className="text-xl mb-12" className="text-lg mb-10 max-w-2xl mx-auto"
style={{ color: 'var(--text-secondary)' }} style={{ color: '#848E9C' }}
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
@@ -77,46 +81,55 @@ export function LandingPage() {
> >
{t('startWithCrypto', language)} {t('startWithCrypto', language)}
</motion.p> </motion.p>
<div className="flex flex-wrap justify-center gap-4">
<motion.div
className="flex flex-col sm:flex-row items-center justify-center gap-4"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
>
<motion.button <motion.button
onClick={() => setShowLoginModal(true)} onClick={() => setShowLoginModal(true)}
className="flex items-center gap-2 px-10 py-4 rounded-lg font-semibold text-lg" className="group flex items-center gap-3 px-8 py-4 rounded-xl font-bold text-lg"
style={{ style={{
background: 'var(--brand-yellow)', background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
color: 'var(--brand-black)', color: '#0B0E11',
}} boxShadow: '0 4px 24px rgba(240, 185, 11, 0.3)',
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
{t('getStartedNow', language)}
<motion.div
animate={{ x: [0, 5, 0] }}
transition={{ duration: 1.5, repeat: Infinity }}
>
<ArrowRight className="w-5 h-5" />
</motion.div>
</motion.button>
<motion.a
href="https://github.com/tinkle-community/nofx/tree/dev"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 px-10 py-4 rounded-lg font-semibold text-lg"
style={{
background: 'transparent',
color: 'var(--brand-light-gray)',
border: '2px solid var(--brand-yellow)',
}} }}
whileHover={{ whileHover={{
scale: 1.05, scale: 1.02,
backgroundColor: 'rgba(240, 185, 11, 0.1)', boxShadow: '0 8px 32px rgba(240, 185, 11, 0.4)',
}} }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.98 }}
> >
{t('getStartedNow', language)}
<ArrowRight className="w-5 h-5 transition-transform group-hover:translate-x-1" />
</motion.button>
<motion.a
href={OFFICIAL_LINKS.github}
target="_blank"
rel="noopener noreferrer"
className="group flex items-center gap-3 px-8 py-4 rounded-xl font-bold text-lg"
style={{
background: 'rgba(255, 255, 255, 0.05)',
color: '#EAECEF',
border: '1px solid rgba(255, 255, 255, 0.1)',
}}
whileHover={{
scale: 1.02,
background: 'rgba(255, 255, 255, 0.08)',
borderColor: 'rgba(240, 185, 11, 0.3)',
}}
whileTap={{ scale: 0.98 }}
>
<Github className="w-5 h-5" />
{t('viewSourceCode', language)} {t('viewSourceCode', language)}
</motion.a> </motion.a>
</div> </motion.div>
</div> </div>
</AnimatedSection> </section>
{showLoginModal && ( {showLoginModal && (
<LoginModal <LoginModal