feat: beginner-first funnel — plain-language guidance from landing to first trade

A newcomer who can't get from install to a running trade in minutes churns.
This pass removes every jargon wall on that path:

- Landing hero: primary CTA is now 'START THE AUTOPILOT' (routes into the
  guided flow; logged-out users get the guest walkthrough) with a
  plain-language promise line — self-hosted, ~$13 to start, no API keys.
  'CREATE STOCK TRADER' demoted to a 'SEE IT WORK' scroll button.
- Deploy section: 'high-frequency trading node / sandboxed execution env'
  copy replaced with plain words plus the first-five-minutes checklist
  (register → fund $1 + $12 → press start).
- Config page: the guided launch panel now renders FIRST for anyone without
  a running autopilot; the config grid becomes detail below. Steps renamed
  to 'Step 1 · Fund the AI wallet ($1+)' etc., each explained in one plain
  sentence with explicit dollar amounts and safety notes ('it can never
  withdraw your money').
- Every AI-wallet action (deposit, create, preflight fix-links) now lands on
  the /welcome page with the QR code and auto-refreshing balance instead of
  the key-config modal.
- README: first-run three-step guide under the install command (banner asset
  included); six dead landing components removed.

Tests: 128 web tests green; funnel click-verified logged-out and logged-in.
This commit is contained in:
tinkle-community
2026-07-07 20:22:59 +09:00
parent c53a563bff
commit c1b660bb45
12 changed files with 271 additions and 1199 deletions

View File

@@ -1,157 +0,0 @@
import { motion } from 'framer-motion'
import { Terminal, Shield, Cpu, BarChart3 } from 'lucide-react'
import { t, Language } from '../../i18n/translations'
interface AboutSectionProps {
language: Language
}
export default function AboutSection({ language }: AboutSectionProps) {
const features = [
{
icon: Shield,
title: language === 'zh' ? 'Full Control' : 'Full Control',
desc: language === 'zh' ? 'Self-hosted, data secure' : 'Self-hosted, data secure',
},
{
icon: Cpu,
title: language === 'zh' ? 'Multi-AI Support' : 'Multi-AI Support',
desc: language === 'zh' ? 'DeepSeek, GPT, Claude...' : 'DeepSeek, GPT, Claude...',
},
{
icon: BarChart3,
title: language === 'zh' ? 'Real-time Monitor' : 'Real-time Monitor',
desc: language === 'zh' ? 'Visual trading dashboard' : 'Visual trading dashboard',
},
]
return (
<section className="py-24 relative overflow-hidden" style={{ background: '#F1ECE2' }}>
{/* Background Decoration */}
<div
className="absolute top-0 right-0 w-96 h-96 rounded-full blur-3xl opacity-30"
style={{ background: 'radial-gradient(circle, rgba(224, 72, 59, 0.08) 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
initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<motion.div
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full mb-6"
style={{
background: 'rgba(224, 72, 59, 0.1)',
border: '1px solid rgba(224, 72, 59, 0.2)',
}}
>
<Terminal className="w-4 h-4" style={{ color: '#E0483B' }} />
<span className="text-xs font-medium" style={{ color: '#E0483B' }}>
{t('aboutNofx', language)}
</span>
</motion.div>
<h2 className="text-4xl lg:text-5xl font-bold mb-6" style={{ color: '#1A1813' }}>
{t('whatIsNofx', language)}
</h2>
<p className="text-lg mb-8 leading-relaxed" style={{ color: '#8A8478' }}>
{t('nofxNotAnotherBot', language)} {t('nofxDescription1', language)}
</p>
{/* Feature Pills */}
<div className="flex flex-wrap gap-3">
{features.map((feature, index) => (
<motion.div
key={feature.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
className="flex items-center gap-3 px-4 py-3 rounded-xl"
style={{
background: '#F7F4EC',
border: '1px solid rgba(26, 24, 19, 0.14)',
}}
>
<div
className="w-10 h-10 rounded-lg flex items-center justify-center"
style={{ background: 'rgba(224, 72, 59, 0.1)' }}
>
<feature.icon className="w-5 h-5" style={{ color: '#E0483B' }} />
</div>
<div>
<div className="text-sm font-semibold" style={{ color: '#1A1813' }}>
{feature.title}
</div>
<div className="text-xs" style={{ color: '#8A8478' }}>
{feature.desc}
</div>
</div>
</motion.div>
))}
</div>
</motion.div>
{/* 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
className="rounded-2xl overflow-hidden"
style={{
background: '#F7F4EC',
border: '1px solid rgba(26, 24, 19, 0.14)',
boxShadow: '0 6px 24px rgba(26, 24, 19, 0.12)',
}}
>
{/* Terminal Header */}
<div
className="flex items-center gap-2 px-4 py-3"
style={{ background: '#E8E2D5', borderBottom: '1px solid rgba(26, 24, 19, 0.14)' }}
>
<div className="flex gap-2">
<div className="w-3 h-3 rounded-full" style={{ background: '#D6433A' }} />
<div className="w-3 h-3 rounded-full" style={{ background: '#E0483B' }} />
<div className="w-3 h-3 rounded-full" style={{ background: '#2E8B57' }} />
</div>
<span className="text-xs ml-2" style={{ color: '#8A8478' }}>terminal</span>
</div>
{/* Terminal Content */}
<div className="p-6 font-mono text-sm space-y-2">
<div style={{ color: '#8A8478' }}>$ git clone https://github.com/NoFxAiOS/nofx.git</div>
<div style={{ color: '#8A8478' }}>$ cd nofx && chmod +x start.sh</div>
<div style={{ color: '#8A8478' }}>$ ./start.sh start --build</div>
<div className="pt-2" style={{ color: '#E0483B' }}>
{t('startupMessages1', language)}
</div>
<div style={{ color: '#2E8B57' }}>
{t('startupMessages2', language)}
</div>
<div style={{ color: '#2E8B57' }}>
{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: '#E0483B' }}></span>
<span style={{ color: '#1A1813' }}>_</span>
</motion.div>
</div>
</div>
</motion.div>
</div>
</div>
</section>
)
}

View File

@@ -1,29 +0,0 @@
import { useRef } from 'react'
import { motion, useInView } from 'framer-motion'
export default function AnimatedSection({
children,
id,
backgroundColor = '#F1ECE2',
}: {
children: React.ReactNode
id?: string
backgroundColor?: string
}) {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, margin: '-100px' })
return (
<motion.section
id={id}
ref={ref}
className="py-20 px-4"
style={{ background: backgroundColor }}
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : { opacity: 0 }}
transition={{ duration: 0.6 }}
>
{children}
</motion.section>
)
}

View File

@@ -1,158 +0,0 @@
import { motion } from 'framer-motion'
import { MessageCircle, Heart, Repeat2, ExternalLink } from 'lucide-react'
import { Language } from '../../i18n/translations'
interface TweetProps {
quote: string
authorName: string
handle: string
avatarUrl: string
tweetUrl: string
delay: number
}
function TweetCard({ quote, authorName, handle, avatarUrl, tweetUrl, delay }: TweetProps) {
return (
<motion.a
href={tweetUrl}
target="_blank"
rel="noopener noreferrer"
className="block p-5 rounded-2xl transition-all duration-300 group"
style={{
background: '#F7F4EC',
border: '1px solid rgba(26, 24, 19, 0.14)',
}}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay }}
whileHover={{
y: -4,
borderColor: 'rgba(224, 72, 59, 0.3)',
}}
>
{/* Header */}
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<img
src={avatarUrl}
alt={authorName}
className="w-10 h-10 rounded-full object-cover"
style={{ border: '2px solid rgba(26, 24, 19, 0.14)' }}
/>
<div>
<div className="font-semibold text-sm" style={{ color: '#1A1813' }}>
{authorName}
</div>
<div className="text-xs" style={{ color: '#8A8478' }}>
{handle}
</div>
</div>
</div>
{/* X Logo */}
<div
className="w-6 h-6 flex items-center justify-center opacity-50 group-hover:opacity-100 transition-opacity"
style={{ color: '#1A1813' }}
>
<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>
</div>
</div>
{/* Content */}
<p
className="text-sm leading-relaxed mb-4 line-clamp-4"
style={{ color: '#1A1813' }}
>
{quote}
</p>
{/* Footer */}
<div className="flex items-center gap-6 pt-3" style={{ borderTop: '1px solid rgba(26, 24, 19, 0.14)' }}>
<div className="flex items-center gap-1.5 text-xs" style={{ color: '#8A8478' }}>
<MessageCircle className="w-3.5 h-3.5" />
<span>Reply</span>
</div>
<div className="flex items-center gap-1.5 text-xs" style={{ color: '#8A8478' }}>
<Repeat2 className="w-3.5 h-3.5" />
<span>Repost</span>
</div>
<div className="flex items-center gap-1.5 text-xs" style={{ color: '#8A8478' }}>
<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: '#E0483B' }} />
</div>
</div>
</motion.a>
)
}
interface CommunitySectionProps {
language?: Language
}
export default function CommunitySection({ language }: CommunitySectionProps) {
const tweets: TweetProps[] = []
return (
<section className="py-24 relative" style={{ background: '#F1ECE2' }}>
{/* 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(224, 72, 59, 0.08) 0%, transparent 70%)' }}
/>
<div className="max-w-6xl mx-auto px-4 relative z-10">
{/* Header */}
<motion.div
className="text-center mb-12"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
>
<h2 className="text-4xl lg:text-5xl font-bold mb-4" style={{ color: '#1A1813' }}>
{language === 'zh' ? 'Community Voices' : 'Community Voices'}
</h2>
<p className="text-lg" style={{ color: '#8A8478' }}>
{language === 'zh' ? 'See what others are saying' : '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://x.com/vergex_ai"
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(224, 72, 59, 0.1)',
color: '#E0483B',
border: '1px solid rgba(224, 72, 59, 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' ? 'Follow us on X' : 'Follow us on X'}
</a>
</motion.div>
</div>
</section>
)
}

View File

@@ -1,203 +0,0 @@
import { motion } from 'framer-motion'
import { Brain, Swords, BarChart3, Shield, Blocks, LineChart } from 'lucide-react'
import { t, Language } from '../../i18n/translations'
interface FeaturesSectionProps {
language: Language
}
export default function FeaturesSection({ language }: FeaturesSectionProps) {
const features = [
{
icon: Brain,
title: language === 'zh' ? 'AI Strategy Orchestration' : 'AI Strategy Orchestration',
desc: language === 'zh'
? 'Support DeepSeek, GPT, Claude, Qwen and more. Custom prompts, AI autonomously analyzes markets and makes trading decisions'
: 'Support DeepSeek, GPT, Claude, Qwen and more. Custom prompts, AI autonomously analyzes markets and makes trading decisions',
highlight: true,
badge: language === 'zh' ? 'Core' : 'Core',
},
{
icon: Swords,
title: language === 'zh' ? 'Multi-AI Arena' : 'Multi-AI Arena',
desc: language === 'zh'
? 'Multiple AI traders compete in real-time, live PnL leaderboard, automatic survival of the fittest'
: 'Multiple AI traders compete in real-time, live PnL leaderboard, automatic survival of the fittest',
highlight: true,
badge: language === 'zh' ? 'Unique' : 'Unique',
},
{
icon: LineChart,
title: language === 'zh' ? 'Pro Quant Data' : 'Pro Quant Data',
desc: language === 'zh'
? 'Integrated candlesticks, indicators, order book, funding rates, open interest - comprehensive data for AI decisions'
: 'Integrated candlesticks, indicators, order book, funding rates, open interest - comprehensive data for AI decisions',
highlight: true,
badge: language === 'zh' ? 'Pro' : 'Pro',
},
{
icon: Blocks,
title: language === 'zh' ? 'Multi-Exchange Support' : 'Multi-Exchange Support',
desc: language === 'zh'
? 'Binance, OKX, Bybit, Hyperliquid, Aster DEX - one system, multiple exchanges'
: 'Binance, OKX, Bybit, Hyperliquid, Aster DEX - one system, multiple exchanges',
},
{
icon: BarChart3,
title: language === 'zh' ? 'Real-time Dashboard' : 'Real-time Dashboard',
desc: language === 'zh'
? 'Trade monitoring, PnL curves, position analysis, AI decision logs at a glance'
: 'Trade monitoring, PnL curves, position analysis, AI decision logs at a glance',
},
{
icon: Shield,
title: language === 'zh' ? 'Open Source & Self-Hosted' : 'Open Source & Self-Hosted',
desc: language === 'zh'
? 'Fully open source, data stored locally, API keys never leave your server'
: 'Fully open source, data stored locally, API keys never leave your server',
},
]
return (
<section className="py-24 relative" style={{ background: '#F1ECE2' }}>
{/* Background */}
<div
className="absolute inset-0 opacity-[0.04]"
style={{
backgroundImage: `linear-gradient(#E0483B 1px, transparent 1px), linear-gradient(90deg, #E0483B 1px, transparent 1px)`,
backgroundSize: '40px 40px',
}}
/>
<div className="max-w-6xl mx-auto px-4 relative z-10">
{/* Header */}
<motion.div
className="text-center mb-16"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
>
<h2 className="text-4xl lg:text-5xl font-bold mb-4" style={{ color: '#1A1813' }}>
{t('whyChooseNofx', language)}
</h2>
<p className="text-lg max-w-2xl mx-auto" style={{ color: '#8A8478' }}>
{language === 'zh'
? 'Not just a trading bot, but a complete AI trading operating system'
: 'Not just a trading bot, but a complete AI trading operating system'}
</p>
</motion.div>
{/* Features Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-5">
{features.map((feature, index) => (
<motion.div
key={feature.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
className={`
relative group rounded-2xl p-6 transition-all duration-300
${feature.highlight ? 'md:col-span-1 lg:col-span-1' : ''}
`}
style={{
background: feature.highlight
? 'rgba(224, 72, 59, 0.06)'
: '#F7F4EC',
border: feature.highlight
? '1px solid rgba(224, 72, 59, 0.2)'
: '1px solid rgba(26, 24, 19, 0.14)',
}}
>
{/* Badge */}
{feature.badge && (
<div
className="absolute top-4 right-4 px-2 py-1 rounded text-xs font-medium"
style={{
background: 'rgba(224, 72, 59, 0.15)',
color: '#E0483B',
}}
>
{feature.badge}
</div>
)}
{/* Icon */}
<motion.div
className="w-12 h-12 rounded-xl flex items-center justify-center mb-4"
style={{
background: feature.highlight
? 'rgba(224, 72, 59, 0.15)'
: 'rgba(224, 72, 59, 0.1)',
border: '1px solid rgba(224, 72, 59, 0.2)',
}}
whileHover={{ scale: 1.1, rotate: 5 }}
>
<feature.icon
className="w-6 h-6"
style={{ color: '#E0483B' }}
/>
</motion.div>
{/* Text */}
<h3
className="text-xl font-bold mb-3"
style={{ color: '#1A1813' }}
>
{feature.title}
</h3>
<p
className="text-sm leading-relaxed"
style={{ color: '#8A8478' }}
>
{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-20 transition-opacity duration-500"
style={{ background: '#E0483B' }}
/>
</motion.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 Models' : 'AI Models' },
{ value: '5+', label: language === 'zh' ? 'Exchanges' : 'Exchanges' },
{ value: '24/7', label: language === 'zh' ? 'Auto Trading' : 'Auto Trading' },
{ value: '100%', label: language === 'zh' ? 'Open Source' : 'Open Source' },
].map((stat) => (
<div
key={stat.label}
className="text-center p-4 rounded-xl"
style={{
background: '#F7F4EC',
border: '1px solid rgba(26, 24, 19, 0.14)',
}}
>
<div
className="text-2xl font-bold mb-1"
style={{
color: '#E0483B',
}}
>
{stat.value}
</div>
<div className="text-xs" style={{ color: '#8A8478' }}>
{stat.label}
</div>
</div>
))}
</motion.div>
</div>
</section>
)
}

View File

@@ -1,259 +0,0 @@
import { motion } from 'framer-motion'
import { ArrowRight, Play, Github, Zap } from 'lucide-react'
import { Link } from 'react-router-dom'
import { t, Language } from '../../i18n/translations'
import { useGitHubStats } from '../../hooks/useGitHubStats'
import { useCounterAnimation } from '../../hooks/useCounterAnimation'
import { OFFICIAL_LINKS } from '../../constants/branding'
interface HeroSectionProps {
language: Language
}
export default function HeroSection({ language }: HeroSectionProps) {
const { stars, daysOld, isLoading } = useGitHubStats('NoFxAiOS', 'nofx')
const animatedStars = useCounterAnimation({
start: 0,
end: stars,
duration: 2000,
})
return (
<section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-16">
{/* Animated Background */}
<div className="absolute inset-0 overflow-hidden">
{/* Grid Pattern */}
<div
className="absolute inset-0 opacity-[0.03]"
style={{
backgroundImage: `linear-gradient(#E0483B 1px, transparent 1px), linear-gradient(90deg, #E0483B 1px, transparent 1px)`,
backgroundSize: '60px 60px',
}}
/>
{/* Radial Gradient */}
<div
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] rounded-full"
style={{
background:
'radial-gradient(circle, rgba(224, 72, 59, 0.08) 0%, transparent 70%)',
}}
/>
{/* Floating Orbs */}
<motion.div
className="absolute top-20 right-20 w-32 h-32 rounded-full blur-3xl"
style={{ background: 'rgba(224, 72, 59, 0.12)' }}
animate={{
y: [0, 30, 0],
scale: [1, 1.1, 1],
}}
transition={{ duration: 8, repeat: Infinity, ease: 'easeInOut' }}
/>
<motion.div
className="absolute bottom-40 left-20 w-48 h-48 rounded-full blur-3xl"
style={{ background: 'rgba(224, 72, 59, 0.08)' }}
animate={{
y: [0, -40, 0],
scale: [1, 1.2, 1],
}}
transition={{ duration: 10, repeat: Infinity, ease: 'easeInOut' }}
/>
</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(224, 72, 59, 0.1)',
border: '1px solid rgba(224, 72, 59, 0.3)',
}}
>
<Zap className="w-4 h-4" style={{ color: '#E0483B' }} />
<span className="text-sm font-medium" style={{ color: '#E0483B' }}>
{isLoading ? (
t('githubStarsInDays', language)
) : language === 'zh' ? (
<>
<span className="font-bold tabular-nums">
{(animatedStars / 1000).toFixed(1)}K+
</span>{' '}
GitHub Stars in {daysOld} days
</>
) : (
<>
<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: '#1A1813' }}>{t('heroTitle1', language)}</span>
<br />
<span
className="relative inline-block"
style={{
color: '#E0483B',
}}
>
{t('heroTitle2', language)}
<motion.span
className="absolute -bottom-2 left-0 h-1 rounded-full"
style={{ background: '#E0483B' }}
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: '#8A8478' }}
>
{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.div
className="group flex items-center gap-3 px-8 py-4 rounded-xl font-bold text-lg transition-all"
style={{
background: '#E0483B',
color: '#F7F4EC',
boxShadow: '0 4px 24px rgba(224, 72, 59, 0.25)',
}}
whileHover={{
scale: 1.02,
boxShadow: '0 8px 32px rgba(224, 72, 59, 0.35)',
}}
whileTap={{ scale: 0.98 }}
>
<Link to="/competition" className="flex items-center gap-3">
<Play className="w-5 h-5" />
{t('liveCompetition', language) || 'Live Competition'}
<ArrowRight className="w-5 h-5 transition-transform group-hover:translate-x-1" />
</Link>
</motion.div>
<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: '#F7F4EC',
color: '#1A1813',
border: '1px solid rgba(26, 24, 19, 0.14)',
}}
whileHover={{
scale: 1.02,
background: '#F2EEE4',
borderColor: 'rgba(224, 72, 59, 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' : 'Exchanges',
value: '5+',
},
{
label: language === 'zh' ? 'AI Models' : 'AI Models',
value: '10+',
},
{
label: language === 'zh' ? 'Open Source' : '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={{
color: '#E0483B',
}}
>
{stat.value}
</div>
<div className="text-sm" style={{ color: '#8A8478' }}>
{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: '#8A8478' }}
>
{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(224, 72, 59, 0.3)' }}
animate={{ y: [0, 8, 0] }}
transition={{ duration: 2, repeat: Infinity }}
>
<motion.div
className="w-1.5 h-3 rounded-full"
style={{ background: '#E0483B' }}
/>
</motion.div>
</motion.div>
</section>
)
}

View File

@@ -1,167 +0,0 @@
import { motion } from 'framer-motion'
import { Download, Rocket, TrendingUp, AlertTriangle } from 'lucide-react'
import { t, Language } from '../../i18n/translations'
interface HowItWorksSectionProps {
language: Language
}
export default function HowItWorksSection({ language }: HowItWorksSectionProps) {
const steps = [
{
icon: Download,
number: '01',
title: language === 'zh' ? 'One-Click Deploy' : 'One-Click Deploy',
desc: language === 'zh'
? 'Run a single command on your server to deploy'
: 'Run a single command on your server to deploy',
code: 'curl -fsSL https://raw.githubusercontent.com/NoFxAiOS/nofx/main/install.sh | bash',
},
{
icon: Rocket,
number: '02',
title: language === 'zh' ? 'Access Dashboard' : 'Access Dashboard',
desc: language === 'zh'
? 'Access your server via browser'
: 'Access your server via browser',
code: 'http://YOUR_SERVER_IP:3000',
},
{
icon: TrendingUp,
number: '03',
title: language === 'zh' ? 'Start Trading' : 'Start Trading',
desc: language === 'zh'
? 'Create trader, let AI do the work'
: 'Create trader, let AI do the work',
code: language === 'zh' ? 'Configure Model → Exchange → Create Trader' : 'Configure Model → Exchange → Create Trader',
},
]
return (
<section className="py-24 relative overflow-hidden" style={{ background: '#F1ECE2' }}>
{/* 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(224, 72, 59, 0.12) 0%, transparent 70%)' }}
/>
<div className="max-w-6xl mx-auto px-4 relative z-10">
{/* Header */}
<motion.div
className="text-center mb-16"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
>
<h2 className="text-4xl lg:text-5xl font-bold mb-4" style={{ color: '#1A1813' }}>
{t('howToStart', language)}
</h2>
<p className="text-lg" style={{ color: '#8A8478' }}>
{t('fourSimpleSteps', language)}
</p>
</motion.div>
{/* Steps Timeline */}
<div className="relative">
{/* Connecting Line */}
<div
className="absolute left-[39px] top-0 bottom-0 w-px hidden lg:block"
style={{ background: 'linear-gradient(to bottom, transparent, rgba(224, 72, 59, 0.3), transparent)' }}
/>
<div className="space-y-6">
{steps.map((step, index) => (
<motion.div
key={step.number}
initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.15 }}
className="relative"
>
<div
className="flex flex-col lg:flex-row items-start gap-6 p-6 rounded-2xl transition-all duration-300 hover:translate-x-2"
style={{
background: '#F7F4EC',
border: '1px solid rgba(26, 24, 19, 0.14)',
}}
>
{/* 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(224, 72, 59, 0.15) 0%, rgba(224, 72, 59, 0.05) 100%)',
border: '1px solid rgba(224, 72, 59, 0.3)',
}}
whileHover={{ scale: 1.1 }}
>
<step.icon className="w-8 h-8" style={{ color: '#E0483B' }} />
</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: '#E0483B' }}
>
{step.number}
</span>
<h3 className="text-xl font-bold" style={{ color: '#1A1813' }}>
{step.title}
</h3>
</div>
<p className="mb-4" style={{ color: '#8A8478' }}>
{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: '#E8E2D5',
border: '1px solid rgba(26, 24, 19, 0.14)',
}}
>
<span style={{ color: '#8A8478' }}>$</span>
<span style={{ color: '#1A1813' }}>{step.code}</span>
</div>
</div>
</div>
</motion.div>
))}
</div>
</div>
{/* Risk Warning */}
<motion.div
className="mt-12 p-6 rounded-2xl flex items-start gap-4"
style={{
background: 'rgba(224, 72, 59, 0.05)',
border: '1px solid rgba(224, 72, 59, 0.15)',
}}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
>
<div
className="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0"
style={{ background: 'rgba(224, 72, 59, 0.1)' }}
>
<AlertTriangle className="w-6 h-6" style={{ color: '#E0483B' }} />
</div>
<div>
<div className="font-semibold mb-2" style={{ color: '#E0483B' }}>
{t('importantRiskWarning', language)}
</div>
<p className="text-sm leading-relaxed" style={{ color: '#8A8478' }}>
{t('riskWarningText', language)}
</p>
</div>
</motion.div>
</div>
</section>
)
}

View File

@@ -31,14 +31,33 @@ export default function DeploymentHub() {
</h2>
<p className="text-nofx-text-muted text-lg leading-relaxed font-light">
Initialize your own high-frequency trading node in seconds.
Our optimized installer handles all dependencies, bringing the trading system online with a single command.
One command on your laptop or any server installs
everything. Open the address it prints, create your
account, and the guided launch takes you to your
first AI trade in about five minutes around $13
is enough to start.
</p>
{/* the first five minutes, in plain words */}
<ol className="space-y-2 pt-2 font-mono text-sm text-nofx-text-muted">
{[
'Register — the first account owns this instance.',
'Fund two small wallets: $1+ for AI fees, $12+ to trade with (guided, with QR codes).',
'Press Start — the AI trades on its own; stop it anytime.',
].map((step, i) => (
<li key={i} className="flex gap-3">
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded border border-nofx-gold/30 bg-nofx-gold/10 text-[11px] font-bold text-nofx-gold">
{i + 1}
</span>
<span>{step}</span>
</li>
))}
</ol>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-4">
{[
{ icon: Command, label: "One-Line Install", desc: "No configuration needed" },
{ icon: Shield, label: "Secure Core", desc: "Sandboxed execution env" }
{ icon: Command, label: "One-Line Install", desc: "Docker handles every dependency" },
{ icon: Shield, label: "Your Keys Stay Home", desc: "Runs on your machine, keys encrypted locally" }
].map((item, i) => (
<div key={i} className="flex gap-4 items-start p-4 rounded bg-nofx-bg-lighter border border-[rgba(26,24,19,0.14)] hover:border-nofx-gold/30 transition-colors group">
<div className="p-2 rounded bg-nofx-bg-deeper border border-[rgba(26,24,19,0.14)] text-nofx-gold group-hover:bg-nofx-gold/10 transition-colors">

View File

@@ -1,6 +1,7 @@
import { motion } from 'framer-motion'
import { ArrowRight, Shield, Activity, CircuitBoard, Wifi, Globe, Zap, Star, GitFork, Users, MessageCircle } from 'lucide-react'
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { useGitHubStats } from '../../../hooks/useGitHubStats'
import AgentTerminal from '../brand/AgentTerminal'
@@ -16,6 +17,7 @@ const tickerLabels: Record<string, string> = {
}
export default function TerminalHero() {
const navigate = useNavigate()
// Real-time price state
const [prices, setPrices] = useState<Record<string, string>>({
@@ -231,17 +233,29 @@ export default function TerminalHero() {
{/* CTA Buttons */}
<div className="flex flex-col sm:flex-row gap-4 w-full justify-center">
<button
onClick={() => document.getElementById('market-scanner')?.scrollIntoView({ behavior: 'smooth' })}
onClick={() => navigate('/traders')}
className="group relative overflow-hidden bg-nofx-gold text-nofx-bg-lighter px-8 py-4 font-bold font-mono tracking-wider hover:scale-105 transition-transform duration-200"
style={{ clipPath: 'polygon(10% 0, 100% 0, 100% 70%, 90% 100%, 0 100%, 0 30%)' }}
>
<span className="relative z-10 flex items-center gap-2">
CREATE STOCK TRADER <ArrowRight className="w-4 h-4" />
START THE AUTOPILOT <ArrowRight className="w-4 h-4" />
</span>
<div className="absolute inset-0 bg-nofx-text/10 translate-y-full group-hover:translate-y-0 transition-transform duration-300"></div>
</button>
<button
onClick={() => document.getElementById('market-scanner')?.scrollIntoView({ behavior: 'smooth' })}
className="px-8 py-4 font-bold font-mono tracking-wider text-nofx-text border border-[rgba(26,24,19,0.2)] rounded hover:border-nofx-gold/50 hover:text-nofx-gold transition-colors"
>
SEE IT WORK
</button>
</div>
{/* Plain-language promise — the anti-jargon line */}
<p className="mt-5 text-sm text-nofx-text-muted font-mono">
Self-hosted &amp; open source · about $13 is enough to start · guided
setup, no API keys first trade in minutes
</p>
{/* Community Stats Row */}
<CommunityStats />

View File

@@ -3,6 +3,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom'
import useSWR from 'swr'
import { api } from '../../lib/api'
import { ApiError } from '../../lib/httpClient'
import { ROUTES } from '../../router/paths'
import type {
TraderInfo,
CreateTraderRequest,
@@ -667,8 +668,9 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
if (!setupTarget) return
if (setupTarget === 'claw402') {
if (supportedModels.length === 0 && allModels.length === 0) return
handleOpenClaw402Config()
// The welcome page shows the deposit QR, auto-creates the wallet if
// needed, and polls the balance — friendlier than the key-config modal.
navigate(ROUTES.welcome)
} else if (setupTarget === 'hyperliquid') {
handleOpenHyperliquidConfig()
} else if (setupTarget === 'hyperliquid-funds') {
@@ -693,6 +695,21 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
await Promise.all([loadConfigs(), mutateTraders()])
}
const hasRunningTrader = (traders || []).some((trader) => trader.is_running)
const launchPanel = (
<AutopilotLaunchPanel
models={allModels}
exchanges={allExchanges}
exchangeAccountStates={exchangeAccountStates}
traders={traders || []}
isLoggedIn={Boolean(user && token)}
language={language}
onRefresh={refreshLaunchState}
onOpenClaw402Config={handleOpenClaw402Config}
onOpenHyperliquidConfig={handleOpenHyperliquidConfig}
/>
)
return (
<DeepVoidBackground className="py-8" disableAnimation>
<div className="w-full px-4 md:px-8 space-y-8 animate-fade-in">
@@ -767,6 +784,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
</div>
</div>
{/* Newcomers see the guided launch FIRST — the config grid is detail.
Once an autopilot is running, the running bot takes back the top. */}
{!hasRunningTrader && launchPanel}
{/* Configuration Status Grid */}
<ConfigStatusGrid
configuredModels={configuredModels}
@@ -784,17 +805,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
onCopyAddress={handleCopyAddress}
/>
<AutopilotLaunchPanel
models={allModels}
exchanges={allExchanges}
exchangeAccountStates={exchangeAccountStates}
traders={traders || []}
isLoggedIn={Boolean(user && token)}
language={language}
onRefresh={refreshLaunchState}
onOpenClaw402Config={handleOpenClaw402Config}
onOpenHyperliquidConfig={handleOpenHyperliquidConfig}
/>
{hasRunningTrader && launchPanel}
{/* Traders List */}
<TradersList

View File

@@ -394,20 +394,20 @@ export function AutopilotLaunchPanel({
action?: JSX.Element
}> = [
{
title: 'AI fee wallet',
title: 'Step 1 · Fund the AI wallet ($1+)',
detail:
'Pays Claw402.ai data and model calls with Base USDC. This is separate from trading collateral.',
'The AI pays a tiny fee each time it thinks. Send $1 or more of USDC on the Base network to this address — from Binance, OKX, Coinbase or any wallet. Separate from your trading money.',
status: feeReady ? 'ready' : 'action',
meta: feeWalletAddress
? `${shortAddress(feeWalletAddress)} · ${formatUSDC(feeWalletBalance)} USDC${
feeReady ? '' : ` · needs ≥ ${minAIFeeUSDC} USDC`
}`
: 'Base USDC wallet required',
: 'Takes 1 minute — we create the wallet for you',
action: feeWalletAddress ? (
<div className="flex flex-wrap items-center gap-3">
<button
type="button"
onClick={() => onOpenClaw402Config?.()}
onClick={() => navigate(ROUTES.welcome)}
className="inline-flex items-center gap-1.5 text-xs font-semibold text-nofx-gold hover:text-nofx-accent"
>
<CircleDollarSign className="h-3.5 w-3.5" />
@@ -425,22 +425,22 @@ export function AutopilotLaunchPanel({
) : (
<button
type="button"
onClick={() => onOpenClaw402Config?.()}
onClick={() => navigate(ROUTES.welcome)}
className="inline-flex items-center gap-1.5 text-xs font-semibold text-nofx-gold hover:text-nofx-accent"
>
<ArrowRight className="h-3.5 w-3.5" />
Open
Create
</button>
),
},
{
title: 'Hyperliquid trading wallet',
title: 'Step 2 · Connect Hyperliquid',
detail:
'Connect an EVM wallet, approve a NOFX Agent, approve the builder fee, then save it to NOFX.',
'Approve NOFX once with your crypto wallet (Rabby or MetaMask). This lets the AI place trades for you — it can never withdraw your money.',
status: hyperliquidConnected ? 'ready' : 'action',
meta: hyperliquidExchange?.hyperliquidWalletAddr
? `${shortAddress(hyperliquidExchange.hyperliquidWalletAddr)} · authorized`
: 'Agent and trading authorization required',
: 'A few clicks + 3 wallet signatures',
action: (
<button
type="button"
@@ -453,9 +453,9 @@ export function AutopilotLaunchPanel({
),
},
{
title: 'Trading balance',
title: 'Step 3 · Add trading money ($12+)',
detail:
'Deposit USDC to Hyperliquid. NOFX uses it as margin for the Claw402 Autopilot strategy.',
'Deposit USDC into your Hyperliquid account. This is what the AI trades with — start small, you can add more anytime.',
status: tradingBalanceReady
? 'ready'
: hyperliquidConnected
@@ -465,18 +465,20 @@ export function AutopilotLaunchPanel({
? `${formatUSDC(tradingBalance)} USDC available${
tradingBalanceReady ? '' : ` · needs ≥ ${minTradingUSDC} USDC`
}`
: 'Connect Hyperliquid first',
: 'Finish step 2 first',
},
{
title: 'NOFX Autopilot',
title: 'Step 4 · Press start',
detail:
'Reads the Claw402 board, fetches Signal Lab and liquidation structure, confirms with candles, then trades full-size 10x only when the setup is strong enough.',
'The AI reads the market every few minutes, picks its trades, and manages them on its own. Watch every decision live on the dashboard — stop it with one click anytime.',
status: allReady ? 'ready' : 'blocked',
meta: autopilotTrader?.is_running
? 'Running'
? 'Running — open the dashboard to watch'
: autopilotTrader
? 'Ready to start'
: 'Ready to create when setup is complete',
: allReady
? 'Everything is ready — press the button'
: 'Unlocks when steps 13 are green',
},
]
@@ -485,16 +487,10 @@ export function AutopilotLaunchPanel({
return (
<button
type="button"
onClick={() => {
if (onOpenClaw402Config) {
onOpenClaw402Config()
} else {
navigate(ROUTES.welcome)
}
}}
onClick={() => navigate(ROUTES.welcome)}
className="inline-flex items-center justify-center gap-2 rounded-lg bg-nofx-gold px-4 py-3 text-sm font-bold text-white hover:bg-nofx-accent"
>
Open Claw402 wallet
Set up the AI wallet
<ArrowRight className="h-4 w-4" />
</button>
)
@@ -584,8 +580,8 @@ export function AutopilotLaunchPanel({
Start NOFX Autopilot in minutes
</h2>
<p className="mt-2 max-w-2xl text-sm leading-6 text-nofx-text-muted">
One strategy, one launch path. Fund the AI fee wallet, authorize
Hyperliquid, deposit USDC, then run the Claw402 Autopilot.
Four small steps, about $13 total. No API keys, no config
files the AI trades for you, and you can stop it anytime.
</p>
</div>
<div className="flex flex-wrap gap-2">