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 (
-
- ⚡ -
+ NoFx Logo

{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 */}
- NOFX + NoFx Logo

@@ -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 */} + + {/* Logo */}
- NOFX + NoFx 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' && ( + + )} + {/* Logo */}
- NOFX + NoFx 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" /> - ⚠️ 启用后将完全替换默认策略 + 启用后将完全替换默认策略
); -} \ 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}" +

+
+
+ + {author} + +
+ + ) +} + +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 ( + + ) +} + 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 为你自动决策、执行和优化交易。 + + +
+ + GitHub Stars + + + GitHub Forks + + + GitHub Contributors + +
+ + + 由 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)} />} + +
+ ) +}