diff --git a/web/src/components/LandingPage.tsx b/web/src/components/LandingPage.tsx index ee8064f3..0e8a2d4c 100644 --- a/web/src/components/LandingPage.tsx +++ b/web/src/components/LandingPage.tsx @@ -15,6 +15,7 @@ import { Cpu, } from 'lucide-react' import { CryptoFeatureCard } from './CryptoFeatureCard' +import Typewriter from './Typewriter' // Animation variants const fadeInUp = { @@ -515,14 +516,19 @@ export function LandingPage() { boxShadow: '0 20px 60px rgba(240, 185, 11, 0.2)', }} > -
-                  {`$ git clone https://github.com/...
-$ docker compose up -d
-🚀 NOFX 已启动
-✓ AI 代理运行中
-✓ 交易所已连接
-✓ 策略已激活`}
-                
+ diff --git a/web/src/components/Typewriter.tsx b/web/src/components/Typewriter.tsx new file mode 100644 index 00000000..e8e33349 --- /dev/null +++ b/web/src/components/Typewriter.tsx @@ -0,0 +1,63 @@ +import { useEffect, useRef, useState } from 'react' + +interface TypewriterProps { + lines: string[] + typingSpeed?: number // 毫秒/字符 + lineDelay?: number // 每行结束的额外等待 + className?: string +} + +export default function Typewriter({ + lines, + typingSpeed = 25, + lineDelay = 400, + className, +}: TypewriterProps) { + const [text, setText] = useState('') + const [showCursor, setShowCursor] = useState(true) + const lineIndexRef = useRef(0) + const charIndexRef = useRef(0) + const timerRef = useRef(null) + const blinkRef = useRef(null) + + useEffect(() => { + function typeNext() { + const currentLine = lines[lineIndexRef.current] ?? '' + if (charIndexRef.current < currentLine.length) { + setText((t) => t + currentLine[charIndexRef.current]) + charIndexRef.current += 1 + timerRef.current = window.setTimeout(typeNext, typingSpeed) + } else { + // 行结束 + if (lineIndexRef.current < lines.length - 1) { + setText((t) => t + '\n') + lineIndexRef.current += 1 + charIndexRef.current = 0 + 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]) + + return ( +
+      {text}
+      
+    
+ ) +}