From daba1bc1132cfda38dcb8237140f0bfd9cdf1d52 Mon Sep 17 00:00:00 2001
From: Ember <197652334@qq.com>
Date: Sat, 1 Nov 2025 23:45:27 +0800
Subject: [PATCH] =?UTF-8?q?feat(web):=20=E8=90=BD=E5=9C=B0=E9=A1=B5?=
=?UTF-8?q?=E2=80=9C=E5=90=AF=E5=8A=A8=E8=BE=93=E5=87=BA=E2=80=9D=E5=8A=A0?=
=?UTF-8?q?=E5=85=A5=E6=89=93=E5=AD=97=E6=9C=BA=E6=95=88=E6=9E=9C=EF=BC=88?=
=?UTF-8?q?Typewriter=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
web/src/components/LandingPage.tsx | 22 +++++++----
web/src/components/Typewriter.tsx | 63 ++++++++++++++++++++++++++++++
2 files changed, 77 insertions(+), 8 deletions(-)
create mode 100644 web/src/components/Typewriter.tsx
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}
+ ▍
+
+ )
+}