diff --git a/web/package-lock.json b/web/package-lock.json
index 32e3c01b..a6afa248 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -8,13 +8,17 @@
"name": "nofx-web",
"version": "1.0.0",
"dependencies": {
+ "@radix-ui/react-slot": "^1.2.3",
+ "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
+ "framer-motion": "^12.23.24",
"lucide-react": "^0.552.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"recharts": "^2.15.2",
"swr": "^2.2.5",
+ "tailwind-merge": "^3.3.1",
"zustand": "^5.0.2"
},
"devDependencies": {
@@ -834,6 +838,39 @@
"node": ">=14"
}
},
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -1504,6 +1541,18 @@
"node": ">= 6"
}
},
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -1905,6 +1954,33 @@
"url": "https://github.com/sponsors/rawify"
}
},
+ "node_modules/framer-motion": {
+ "version": "12.23.24",
+ "resolved": "https://registry.npmmirror.com/framer-motion/-/framer-motion-12.23.24.tgz",
+ "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.23.23",
+ "motion-utils": "^12.23.6",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2212,6 +2288,21 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/motion-dom": {
+ "version": "12.23.23",
+ "resolved": "https://registry.npmmirror.com/motion-dom/-/motion-dom-12.23.23.tgz",
+ "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.23.6"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.23.6",
+ "resolved": "https://registry.npmmirror.com/motion-utils/-/motion-utils-12.23.6.tgz",
+ "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2970,6 +3061,16 @@
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/tailwind-merge": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-3.3.1.tgz",
+ "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "3.4.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
@@ -3096,6 +3197,12 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
diff --git a/web/package.json b/web/package.json
index dfe57495..ed1c0732 100644
--- a/web/package.json
+++ b/web/package.json
@@ -8,13 +8,17 @@
"preview": "vite preview"
},
"dependencies": {
+ "@radix-ui/react-slot": "^1.2.3",
+ "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
+ "framer-motion": "^12.23.24",
"lucide-react": "^0.552.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"recharts": "^2.15.2",
"swr": "^2.2.5",
+ "tailwind-merge": "^3.3.1",
"zustand": "^5.0.2"
},
"devDependencies": {
diff --git a/web/public/images/main.png b/web/public/images/main.png
new file mode 100644
index 00000000..3188575c
Binary files /dev/null and b/web/public/images/main.png differ
diff --git a/web/src/App.tsx b/web/src/App.tsx
index a4b627f2..6f785908 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -6,11 +6,13 @@ import { AITradersPage } from './components/AITradersPage';
import { LoginPage } from './components/LoginPage';
import { RegisterPage } from './components/RegisterPage';
import { CompetitionPage } from './components/CompetitionPage';
+import { LandingPage } from './pages/LandingPage';
import AILearning from './components/AILearning';
import { LanguageProvider, useLanguage } from './contexts/LanguageContext';
import { AuthProvider, useAuth } from './contexts/AuthContext';
import { t, type Language } from './i18n/translations';
import { useSystemConfig } from './hooks/useSystemConfig';
+import { Zap } from 'lucide-react';
import type {
SystemStatus,
AccountInfo,
@@ -169,22 +171,23 @@ function App() {
return (
-
- ⚡
-
+
{t('loading', language)}
);
}
- // If not in admin mode and not authenticated, show login/register pages
+ // Show landing page for root route when not authenticated
if (!systemConfig?.admin_mode && (!user || !token)) {
+ if (route === '/login') {
+ return ;
+ }
if (route === '/register') {
return ;
}
- return ;
+ // Default to landing page when not authenticated
+ return ;
}
return (
@@ -258,7 +261,8 @@ function App() {
{/* Admin Mode Indicator */}
{systemConfig?.admin_mode && (
- ⚡ {t('adminMode', language)}
+
+ {t('adminMode', language)}
)}
diff --git a/web/src/components/AITradersPage.tsx b/web/src/components/AITradersPage.tsx
index d5937fb0..3a947df3 100644
--- a/web/src/components/AITradersPage.tsx
+++ b/web/src/components/AITradersPage.tsx
@@ -7,7 +7,7 @@ import { t, type Language } from '../i18n/translations';
import { getExchangeIcon } from './ExchangeIcons';
import { getModelIcon } from './ModelIcons';
import { TraderConfigModal } from './TraderConfigModal';
-import { Bot, Brain, Landmark, BarChart3, Trash2, Plus, Users } from 'lucide-react';
+import { Bot, Brain, Landmark, BarChart3, Trash2, Plus, Users, AlertTriangle } from 'lucide-react';
// 获取友好的AI模型名称
function getModelDisplayName(modelId: string): string {
@@ -1427,7 +1427,7 @@ function ExchangeConfigModal({
- ⚠️ {t('securityWarning', language)}
+
{t('securityWarning', language)}
{t('exchangeConfigWarning1', language)}
@@ -1467,4 +1467,4 @@ function ExchangeConfigModal({
);
-}
\ No newline at end of file
+}
diff --git a/web/src/components/ComparisonChart.tsx b/web/src/components/ComparisonChart.tsx
index ad89d45a..e8d1fafe 100644
--- a/web/src/components/ComparisonChart.tsx
+++ b/web/src/components/ComparisonChart.tsx
@@ -16,6 +16,7 @@ import type { CompetitionTraderData } from '../types';
import { getTraderColor } from '../utils/traderColors';
import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations';
+import { BarChart3 } from 'lucide-react';
interface ComparisonChartProps {
traders: CompetitionTraderData[];
@@ -136,7 +137,7 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
if (combinedData.length === 0) {
return (
-
📊
+
{t('noHistoricalData', language)}
{t('dataWillAppear', language)}
@@ -338,4 +339,4 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
);
-}
\ No newline at end of file
+}
diff --git a/web/src/components/CompetitionPage.tsx b/web/src/components/CompetitionPage.tsx
index 193b4cce..1ebdb564 100644
--- a/web/src/components/CompetitionPage.tsx
+++ b/web/src/components/CompetitionPage.tsx
@@ -1,4 +1,5 @@
import { useState } from 'react';
+import { Trophy, Medal } from 'lucide-react';
import useSWR from 'swr';
import { api } from '../lib/api';
import type { CompetitionData } from '../types';
@@ -74,11 +75,8 @@ export function CompetitionPage() {
{/* Competition Header - 精简版 */}
-
- 🏆
+
+
@@ -145,8 +143,8 @@ export function CompetitionPage() {
{/* Rank & Name */}
-
- {index === 0 ? '🥇' : index === 1 ? '🥈' : '🥉'}
+
+
{trader.trader_name}
@@ -281,4 +279,4 @@ export function CompetitionPage() {
/>
);
-}
\ No newline at end of file
+}
diff --git a/web/src/components/CryptoFeatureCard.tsx b/web/src/components/CryptoFeatureCard.tsx
new file mode 100644
index 00000000..9c78960a
--- /dev/null
+++ b/web/src/components/CryptoFeatureCard.tsx
@@ -0,0 +1,115 @@
+import * as React from "react";
+import { motion } from "framer-motion";
+import { Check } from "lucide-react";
+import { cn } from "../lib/utils";
+
+interface CryptoFeatureCardProps {
+ icon: React.ReactNode;
+ title: string;
+ description: string;
+ features: string[];
+ className?: string;
+ delay?: number;
+}
+
+export const CryptoFeatureCard = React.forwardRef
(
+ ({ icon, title, description, features, className, delay = 0 }, ref) => {
+ const [isHovered, setIsHovered] = React.useState(false);
+
+ return (
+ setIsHovered(true)}
+ onHoverEnd={() => setIsHovered(false)}
+ className="relative h-full"
+ >
+
+ {/* Animated glow border effect */}
+
+
+
+
+ {/* Background pattern */}
+
+
+
+ {/* Icon container */}
+
+ {icon}
+
+
+ {/* Title */}
+
{title}
+
+ {/* Description */}
+
{description}
+
+ {/* Features list */}
+
+ {features.map((feature, index) => (
+
+
+ {feature}
+
+ ))}
+
+
+
+
+
+
+ );
+ }
+);
+
+CryptoFeatureCard.displayName = "CryptoFeatureCard";
diff --git a/web/src/components/Header.tsx b/web/src/components/Header.tsx
index 3911cddd..48dc6a5b 100644
--- a/web/src/components/Header.tsx
+++ b/web/src/components/Header.tsx
@@ -15,7 +15,7 @@ export function Header({ simple = false }: HeaderProps) {
{/* Left - Logo and Title */}
-
+
@@ -56,4 +56,4 @@ export function Header({ simple = false }: HeaderProps) {
);
-}
\ No newline at end of file
+}
diff --git a/web/src/components/LoginPage.tsx b/web/src/components/LoginPage.tsx
index d2a9cb23..ea36356c 100644
--- a/web/src/components/LoginPage.tsx
+++ b/web/src/components/LoginPage.tsx
@@ -3,6 +3,7 @@ import { useAuth } from '../contexts/AuthContext';
import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations';
import { Header } from './Header';
+import { ArrowLeft } from 'lucide-react';
export function LoginPage() {
const { language } = useLanguage();
@@ -52,13 +53,26 @@ export function LoginPage() {
return (
-
+
+ {/* Back to Home */}
+
{
+ window.history.pushState({}, '', '/');
+ window.dispatchEvent(new PopStateEvent('popstate'));
+ }}
+ className="flex items-center gap-2 mb-6 text-sm hover:text-[#F0B90B] transition-colors"
+ style={{ color: '#848E9C' }}
+ >
+
+ 返回首页
+
+
{/* Logo */}
-
+
{t('loginTitle', language)}
@@ -191,4 +205,4 @@ export function LoginPage() {
);
-}
\ No newline at end of file
+}
diff --git a/web/src/components/RegisterPage.tsx b/web/src/components/RegisterPage.tsx
index 01691aac..438bed05 100644
--- a/web/src/components/RegisterPage.tsx
+++ b/web/src/components/RegisterPage.tsx
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations';
+import { ArrowLeft } from 'lucide-react';
export function RegisterPage() {
const { language } = useLanguage();
@@ -73,10 +74,25 @@ export function RegisterPage() {
return (
+ {/* Back to Home */}
+ {step === 'register' && (
+
{
+ window.history.pushState({}, '', '/');
+ window.dispatchEvent(new PopStateEvent('popstate'));
+ }}
+ className="flex items-center gap-2 mb-6 text-sm hover:text-[#F0B90B] transition-colors"
+ style={{ color: '#848E9C' }}
+ >
+
+ 返回首页
+
+ )}
+
{/* Logo */}
-
+
{t('appTitle', language)}
@@ -307,4 +323,4 @@ export function RegisterPage() {
);
-}
\ No newline at end of file
+}
diff --git a/web/src/components/TraderConfigModal.tsx b/web/src/components/TraderConfigModal.tsx
index c50d5dea..528f763f 100644
--- a/web/src/components/TraderConfigModal.tsx
+++ b/web/src/components/TraderConfigModal.tsx
@@ -453,7 +453,7 @@ export function TraderConfigModal({
className="w-4 h-4"
/>
覆盖默认提示词
-
⚠️ 启用后将完全替换默认策略
+
启用后将完全替换默认策略
@@ -491,4 +491,4 @@ export function TraderConfigModal({
);
-}
\ No newline at end of file
+}
diff --git a/web/src/components/Typewriter.tsx b/web/src/components/Typewriter.tsx
new file mode 100644
index 00000000..3d1ff93f
--- /dev/null
+++ b/web/src/components/Typewriter.tsx
@@ -0,0 +1,73 @@
+import { useEffect, useMemo, useRef, useState } from 'react'
+
+interface TypewriterProps {
+ lines: string[]
+ typingSpeed?: number // 毫秒/字符
+ lineDelay?: number // 每行结束的额外等待
+ className?: string
+ style?: React.CSSProperties
+}
+
+export default function Typewriter({
+ lines,
+ typingSpeed = 50,
+ lineDelay = 600,
+ className,
+ style,
+}: TypewriterProps) {
+ const [typedLines, setTypedLines] = useState
([''])
+ const [showCursor, setShowCursor] = useState(true)
+ const lineIndexRef = useRef(0)
+ const charIndexRef = useRef(0)
+ const timerRef = useRef(null)
+ const blinkRef = useRef(null)
+ const sanitizedLines = useMemo(() => lines.map((l) => String(l ?? '')), [lines])
+
+ useEffect(() => {
+ function typeNext() {
+ const currentLine = sanitizedLines[lineIndexRef.current] ?? ''
+ if (charIndexRef.current < currentLine.length) {
+ setTypedLines((prev) => {
+ const next = [...prev]
+ const ch = currentLine.charAt(charIndexRef.current)
+ next[next.length - 1] = (next[next.length - 1] || '') + ch
+ return next
+ })
+ charIndexRef.current += 1
+ timerRef.current = window.setTimeout(typeNext, typingSpeed)
+ } else {
+ // 行结束
+ if (lineIndexRef.current < sanitizedLines.length - 1) {
+ lineIndexRef.current += 1
+ charIndexRef.current = 0
+ setTypedLines((prev) => [...prev, ''])
+ timerRef.current = window.setTimeout(typeNext, lineDelay)
+ } else {
+ // 最后一行输入完毕
+ timerRef.current = null
+ }
+ }
+ }
+
+ typeNext()
+
+ // 光标闪烁
+ blinkRef.current = window.setInterval(() => {
+ setShowCursor((v) => !v)
+ }, 500)
+
+ return () => {
+ if (timerRef.current) window.clearTimeout(timerRef.current)
+ if (blinkRef.current) window.clearInterval(blinkRef.current)
+ }
+ }, [lines, typingSpeed, lineDelay])
+
+ const displayText = useMemo(() => typedLines.join('\n').replace(/undefined/g, ''), [typedLines])
+
+ return (
+
+ {displayText}
+ ▍
+
+ )
+}
diff --git a/web/src/components/landing/AboutSection.tsx b/web/src/components/landing/AboutSection.tsx
new file mode 100644
index 00000000..d087a66f
--- /dev/null
+++ b/web/src/components/landing/AboutSection.tsx
@@ -0,0 +1,123 @@
+import { motion } from 'framer-motion'
+import { Shield, Target } from 'lucide-react'
+import AnimatedSection from './AnimatedSection'
+import Typewriter from '../Typewriter'
+
+export default function AboutSection() {
+ return (
+
+
+
+
+
+
+
+ 关于 NOFX
+
+
+
+
+ 什么是 NOFX?
+
+
+ NOFX 不是另一个交易机器人,而是 AI 交易的 'Linux' ——
+ 一个透明、可信任的开源 OS,提供统一的 '决策-风险-执行'
+ 层,支持所有资产类别。
+
+
+ 从加密市场起步(24/7、高波动性完美测试场),未来扩展到股票、期货、外汇。核心:开放架构、AI
+ 达尔文主义(多代理自竞争、策略进化)、CodeFi 飞轮(开发者 PR
+ 贡献获积分奖励)。
+
+
+
+
+
+
+
+ 你 100% 掌控
+
+
+ 完全掌控 AI 提示词和资金
+
+
+
+
+
+
+
+
+
+ )
+}
+
diff --git a/web/src/components/landing/AnimatedSection.tsx b/web/src/components/landing/AnimatedSection.tsx
new file mode 100644
index 00000000..2b84c6f4
--- /dev/null
+++ b/web/src/components/landing/AnimatedSection.tsx
@@ -0,0 +1,30 @@
+import { useRef } from 'react'
+import { motion, useInView } from 'framer-motion'
+
+export default function AnimatedSection({
+ children,
+ id,
+ backgroundColor = 'var(--brand-black)',
+}: {
+ children: React.ReactNode
+ id?: string
+ backgroundColor?: string
+}) {
+ const ref = useRef(null)
+ const isInView = useInView(ref, { once: true, margin: '-100px' })
+
+ return (
+
+ {children}
+
+ )
+}
+
diff --git a/web/src/components/landing/CommunitySection.tsx b/web/src/components/landing/CommunitySection.tsx
new file mode 100644
index 00000000..196b286a
--- /dev/null
+++ b/web/src/components/landing/CommunitySection.tsx
@@ -0,0 +1,42 @@
+import { motion } from 'framer-motion'
+import AnimatedSection from './AnimatedSection'
+
+function TestimonialCard({ quote, author, delay }: any) {
+ return (
+
+
+ "{quote}"
+
+
+
+ )
+}
+
+export default function CommunitySection() {
+ const staggerContainer = { animate: { transition: { staggerChildren: 0.1 } } }
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
+
diff --git a/web/src/components/landing/FeaturesSection.tsx b/web/src/components/landing/FeaturesSection.tsx
new file mode 100644
index 00000000..7eef7e00
--- /dev/null
+++ b/web/src/components/landing/FeaturesSection.tsx
@@ -0,0 +1,56 @@
+import { motion } from 'framer-motion'
+import AnimatedSection from './AnimatedSection'
+import { CryptoFeatureCard } from '../CryptoFeatureCard'
+import { Code, Cpu, Lock, Rocket } from 'lucide-react'
+
+export default function FeaturesSection() {
+ return (
+
+
+
+
+
+
+ 核心功能
+
+
+
+ 为什么选择 NOFX?
+
+
+ 开源、透明、社区驱动的 AI 交易操作系统
+
+
+
+
+ }
+ title='100% 开源与自托管'
+ description='你的框架,你的规则。非黑箱,支持自定义提示词和多模型。'
+ features={['完全开源代码', '支持自托管部署', '自定义 AI 提示词', '多模型支持(DeepSeek、Qwen)']}
+ delay={0}
+ />
+ }
+ title='多代理智能竞争'
+ description='AI 策略在沙盒中高速战斗,最优者生存,实现策略进化。'
+ features={['多 AI 代理并行运行', '策略自动优化', '沙盒安全测试', '跨市场策略移植']}
+ delay={0.1}
+ />
+ }
+ title='安全可靠交易'
+ description='企业级安全保障,完全掌控你的资金和交易策略。'
+ features={['本地私钥管理', 'API 权限精细控制', '实时风险监控', '交易日志审计']}
+ delay={0.2}
+ />
+
+
+
+ )
+}
+
diff --git a/web/src/components/landing/FooterSection.tsx b/web/src/components/landing/FooterSection.tsx
new file mode 100644
index 00000000..6ff3faa9
--- /dev/null
+++ b/web/src/components/landing/FooterSection.tsx
@@ -0,0 +1,169 @@
+import { useLanguage } from '../../contexts/LanguageContext'
+import { t } from '../../i18n/translations'
+
+export default function FooterSection() {
+ const { language } = useLanguage()
+ return (
+
+ )
+}
diff --git a/web/src/components/landing/HeaderBar.tsx b/web/src/components/landing/HeaderBar.tsx
new file mode 100644
index 00000000..b3f10049
--- /dev/null
+++ b/web/src/components/landing/HeaderBar.tsx
@@ -0,0 +1,97 @@
+import { useState } from 'react'
+import { motion } from 'framer-motion'
+import { Menu, X } from 'lucide-react'
+
+export default function HeaderBar({ onLoginClick }: { onLoginClick: () => void }) {
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
+
+ return (
+
+
+
+ {/* Logo */}
+
+
+
+ NOFX
+
+
+ Agentic Trading OS
+
+
+
+ {/* Desktop Menu */}
+
+ {['功能', '如何运作', 'GitHub', '社区'].map((item) => (
+
+ {item}
+
+
+ ))}
+
+ 登录 / 注册
+
+
+
+ {/* Mobile Menu Button */}
+
setMobileMenuOpen(!mobileMenuOpen)}
+ className='md:hidden'
+ style={{ color: 'var(--brand-light-gray)' }}
+ whileTap={{ scale: 0.9 }}
+ >
+ {mobileMenuOpen ? : }
+
+
+
+
+ {/* Mobile Menu */}
+
+
+ {['功能', '如何运作', 'GitHub', '社区'].map((item) => (
+
+ {item}
+
+ ))}
+
{
+ onLoginClick()
+ setMobileMenuOpen(false)
+ }}
+ className='w-full px-4 py-2 rounded font-semibold text-sm mt-2'
+ style={{ background: 'var(--brand-yellow)', color: 'var(--brand-black)' }}
+ >
+ 登录 / 注册
+
+
+
+
+ )
+}
+
diff --git a/web/src/components/landing/HeroSection.tsx b/web/src/components/landing/HeroSection.tsx
new file mode 100644
index 00000000..61de5e7a
--- /dev/null
+++ b/web/src/components/landing/HeroSection.tsx
@@ -0,0 +1,82 @@
+import { motion, useScroll, useTransform } from 'framer-motion'
+import { Sparkles } from 'lucide-react'
+
+export default function HeroSection() {
+ const { scrollYProgress } = useScroll()
+ const opacity = useTransform(scrollYProgress, [0, 0.2], [1, 0])
+ const scale = useTransform(scrollYProgress, [0, 0.2], [1, 0.8])
+
+ 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 (
+
+
+
+ {/* Left Content */}
+
+
+
+
+
+ 3 天内 2.5K+ GitHub Stars
+
+
+
+
+
+ Read the Market.
+
+ Write the Trade.
+
+
+
+ NOFX 是 AI 交易的未来标准——一个开放、社区驱动的代理式交易操作系统。支持 Binance、Aster DEX 等交易所,
+ 自托管、多代理竞争,让 AI 为你自动决策、执行和优化交易。
+
+
+
+
+
+ 由 Aster DEX 和 Binance 提供支持,Amber.ac 战略投资。
+
+
+
+ {/* Right Visual */}
+
+
+
+
+ )
+}
+
diff --git a/web/src/components/landing/HowItWorksSection.tsx b/web/src/components/landing/HowItWorksSection.tsx
new file mode 100644
index 00000000..f33075e7
--- /dev/null
+++ b/web/src/components/landing/HowItWorksSection.tsx
@@ -0,0 +1,74 @@
+import { motion } from 'framer-motion'
+import AnimatedSection from './AnimatedSection'
+
+function StepCard({ number, title, description, delay }: any) {
+ return (
+
+
+ {number}
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+ )
+}
+
+export default function HowItWorksSection() {
+ return (
+
+
+
+
+ 如何开始使用 NOFX
+
+
+ 四个简单步骤,开启 AI 自动交易之旅
+
+
+
+
+ {[
+ { number: 1, title: '拉取 GitHub 仓库', description: 'git clone https://github.com/tinkle-community/nofx 并切换到 dev 分支测试新功能。' },
+ { number: 2, title: '配置环境', description: '前端设置交易所 API(如 Binance、Hyperliquid)、AI 模型和自定义提示词。' },
+ { number: 3, title: '部署与运行', description: '一键 Docker 部署,启动 AI 代理。注意:高风险市场,仅用闲钱测试。' },
+ { number: 4, title: '优化与贡献', description: '监控交易,提交 PR 改进框架。加入 Telegram 分享策略。' },
+ ].map((step, index) => (
+
+ ))}
+
+
+
+
+
+
+ 重要风险提示
+
+
+ dev 分支不稳定,勿用无法承受损失的资金。NOFX 非托管,无官方策略。交易有风险,投资需谨慎。
+
+
+
+
+
+ )
+}
diff --git a/web/src/components/landing/LoginModal.tsx b/web/src/components/landing/LoginModal.tsx
new file mode 100644
index 00000000..c981b506
--- /dev/null
+++ b/web/src/components/landing/LoginModal.tsx
@@ -0,0 +1,63 @@
+import { motion } from 'framer-motion'
+import { X } from 'lucide-react'
+
+export default function LoginModal({ onClose }: { onClose: () => void }) {
+ return (
+
+ e.stopPropagation()}
+ >
+
+
+
+
+ 访问 NOFX 平台
+
+
+ 请选择登录或注册以访问完整的 AI 交易平台
+
+
+ {
+ window.history.pushState({}, '', '/login')
+ window.dispatchEvent(new PopStateEvent('popstate'))
+ onClose()
+ }}
+ className='block w-full px-6 py-3 rounded-lg font-semibold text-center'
+ style={{ background: 'var(--brand-yellow)', color: 'var(--brand-black)' }}
+ whileHover={{ scale: 1.05, boxShadow: '0 10px 30px rgba(240, 185, 11, 0.4)' }}
+ whileTap={{ scale: 0.95 }}
+ >
+ 登录
+
+ {
+ window.history.pushState({}, '', '/register')
+ window.dispatchEvent(new PopStateEvent('popstate'))
+ onClose()
+ }}
+ className='block w-full px-6 py-3 rounded-lg font-semibold text-center'
+ style={{ background: 'var(--brand-dark-gray)', color: 'var(--brand-light-gray)', border: '1px solid rgba(240, 185, 11, 0.2)' }}
+ whileHover={{ scale: 1.05, borderColor: 'var(--brand-yellow)' }}
+ whileTap={{ scale: 0.95 }}
+ >
+ 注册新账号
+
+
+
+
+ )
+}
+
diff --git a/web/src/index.css b/web/src/index.css
index 09daf5a6..cc360ff5 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -6,13 +6,22 @@
@tailwind utilities;
:root {
+ /* Binance Brand Colors */
+ --brand-yellow: #F0B90B;
+ --brand-black: #0C0E12;
+ --brand-dark-gray: #1E2329;
+ --brand-light-gray: #EAECEF;
+ --brand-almost-white: #FAFAFA;
+ --brand-white: #FFFFFF;
+
/* Binance Theme Colors */
--binance-yellow: #F0B90B;
--binance-yellow-dark: #C99400;
--binance-yellow-light: #FCD535;
--binance-yellow-glow: rgba(240, 185, 11, 0.2);
- --background: #0B0E11;
+ --background: #181A20; /* Binance body bg */
+ --header-bg: #0B0E11; /* Binance header bg */
--background-elevated: #181A20;
--foreground: #EAECEF;
--panel-bg: #1E2329;
@@ -138,10 +147,10 @@ body {
@keyframes shimmer {
0% {
- background-position: -200% 0;
+ transform: translateX(-100%);
}
100% {
- background-position: 200% 0;
+ transform: translateX(100%);
}
}
@@ -171,12 +180,9 @@ body {
}
/* Glass effect - Binance header style */
-.glass {
- background: var(--background-elevated);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
+.header-bar {
+ background: var(--header-bg);
border-bottom: 1px solid var(--panel-border);
- box-shadow: var(--shadow-sm);
}
/* Monospace numbers */
diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts
new file mode 100644
index 00000000..a5ef1935
--- /dev/null
+++ b/web/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/web/src/pages/LandingPage.tsx b/web/src/pages/LandingPage.tsx
new file mode 100644
index 00000000..206e2b3b
--- /dev/null
+++ b/web/src/pages/LandingPage.tsx
@@ -0,0 +1,52 @@
+import { useState } from 'react'
+import { motion } from 'framer-motion'
+import { ArrowRight } from 'lucide-react'
+import HeaderBar from '../components/landing/HeaderBar'
+import HeroSection from '../components/landing/HeroSection'
+import AboutSection from '../components/landing/AboutSection'
+import FeaturesSection from '../components/landing/FeaturesSection'
+import HowItWorksSection from '../components/landing/HowItWorksSection'
+import CommunitySection from '../components/landing/CommunitySection'
+import AnimatedSection from '../components/landing/AnimatedSection'
+import LoginModal from '../components/landing/LoginModal'
+import FooterSection from '../components/landing/FooterSection'
+
+export function LandingPage() {
+ const [showLoginModal, setShowLoginModal] = useState(false)
+ return (
+
+
setShowLoginModal(true)} />
+
+
+
+
+
+
+ {/* CTA */}
+
+
+
+ 准备好定义 AI 交易的未来吗?
+
+
+ 从加密市场起步,扩展到 TradFi。NOFX 是 AgentFi 的基础架构。
+
+
+
setShowLoginModal(true)} className='flex items-center gap-2 px-10 py-4 rounded-lg font-semibold text-lg' style={{ background: 'var(--brand-yellow)', color: 'var(--brand-black)' }} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
+ 立即开始
+
+
+
+
+
+ 查看源码
+
+
+
+
+
+ {showLoginModal && setShowLoginModal(false)} />}
+
+
+ )
+}