Skip to content

Commit 63e6dbc

Browse files
Spectualclaude
andcommitted
移动端优化 + 视觉增强:响应式布局、Boot跳过、动画升级
移动端优化: - 导航栏:移动端隐藏红黄绿圆点和 spectual@github.io,Tab 字号/间距响应式缩小 - ProfileSection:neofetch 布局移动端垂直居中,技能文字 flex-wrap 换行显示 - ChatSection:快捷命令移动端改为 2 列 grid,[enter] 按钮移动端隐藏,聊天高度用 dvh 单位 - GetInTouchSection:name/company 输入框移动端单列,外层双栏间距响应式 - 所有页面(Index/Resume/Projects)同步移动端导航改动 视觉增强: - BootSequence:加入 scanline 扫描线效果,按任意键可跳过(含移动端触摸),"Press any key to skip..." 提示 - MatrixRain:支持 prefers-reduced-motion 媒体查询 - ProfileSection:头像加 ASCII 终端边框(┌────┐),技能进度条改用 IntersectionObserver 触发逐字填充动画 - AutoTypeCLI:打字速度随机化(50-150ms/char),命令完成后显示模拟输出,再删除进入下一条 - ChatSection:新消息出现时 slideUp 动画从下方滑入 - index.css:新增 slideUp、scanline、prefers-reduced-motion 全局样式 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4b2f972 commit 63e6dbc

File tree

9 files changed

+276
-81
lines changed

9 files changed

+276
-81
lines changed

src/components/BootSequence.tsx

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from "react";
1+
import { useState, useEffect, useRef, useCallback } from "react";
22

33
const BOOT_LINES = [
44
{ text: "[ OK ] Starting neural network...", color: "var(--term-green)" },
@@ -17,21 +17,49 @@ interface BootSequenceProps {
1717
const BootSequence = ({ onComplete }: BootSequenceProps) => {
1818
const [visibleLines, setVisibleLines] = useState(0);
1919
const [fading, setFading] = useState(false);
20+
const completedRef = useRef(false);
21+
const timersRef = useRef<ReturnType<typeof setTimeout>[]>([]);
22+
23+
const finish = useCallback(() => {
24+
if (completedRef.current) return;
25+
completedRef.current = true;
26+
timersRef.current.forEach(clearTimeout);
27+
setFading(true);
28+
setTimeout(onComplete, 450);
29+
}, [onComplete]);
2030

2131
useEffect(() => {
22-
const timers: ReturnType<typeof setTimeout>[] = [];
32+
const timers = timersRef.current;
2333

2434
BOOT_LINES.forEach((_, i) => {
25-
timers.push(setTimeout(() => setVisibleLines(i + 1), i * 340 + 150));
35+
const t = setTimeout(() => setVisibleLines(i + 1), i * 340 + 150);
36+
timers.push(t);
2637
});
2738

2839
const totalTime = BOOT_LINES.length * 340 + 150;
29-
timers.push(setTimeout(() => setFading(true), totalTime + 400));
30-
timers.push(setTimeout(() => onComplete(), totalTime + 850));
40+
const t1 = setTimeout(() => {
41+
if (!completedRef.current) setFading(true);
42+
}, totalTime + 400);
43+
const t2 = setTimeout(() => {
44+
if (!completedRef.current) {
45+
completedRef.current = true;
46+
onComplete();
47+
}
48+
}, totalTime + 850);
49+
timers.push(t1, t2);
3150

3251
return () => timers.forEach(clearTimeout);
3352
}, [onComplete]);
3453

54+
useEffect(() => {
55+
window.addEventListener("keydown", finish);
56+
window.addEventListener("touchstart", finish);
57+
return () => {
58+
window.removeEventListener("keydown", finish);
59+
window.removeEventListener("touchstart", finish);
60+
};
61+
}, [finish]);
62+
3563
return (
3664
<div
3765
style={{
@@ -47,7 +75,10 @@ const BootSequence = ({ onComplete }: BootSequenceProps) => {
4775
pointerEvents: fading ? "none" : "auto",
4876
}}
4977
>
50-
<div style={{ fontFamily: "inherit", fontSize: "13px", minWidth: "320px" }}>
78+
{/* Scanline overlay */}
79+
<div className="boot-scanline-wrap" />
80+
81+
<div style={{ fontFamily: "inherit", fontSize: "13px", minWidth: "320px", position: "relative", zIndex: 2 }}>
5182
{BOOT_LINES.slice(0, visibleLines).map((line, i) => (
5283
<div
5384
key={i}
@@ -63,6 +94,9 @@ const BootSequence = ({ onComplete }: BootSequenceProps) => {
6394
{visibleLines > 0 && visibleLines <= BOOT_LINES.length && (
6495
<span className="cursor-blink" />
6596
)}
97+
<div style={{ color: "var(--term-dim)", fontSize: "11px", marginTop: "24px" }}>
98+
Press any key to skip...
99+
</div>
66100
</div>
67101
</div>
68102
);

src/components/ChatSection.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,20 +262,21 @@ const ChatSection = () => {
262262
ref={scrollContainerRef}
263263
className="terminal-scroll"
264264
style={{
265-
height: "384px",
265+
height: "min(384px, 50dvh)",
266266
overflowY: "auto",
267267
padding: "16px",
268268
fontFamily: "inherit",
269269
fontSize: "13px",
270270
}}
271271
>
272272
{messages.map((message) => (
273-
<MessageBubble
274-
key={message.id}
275-
message={message}
276-
isTyping={typingMessageId === message.id}
277-
onTypingComplete={() => setTypingMessageId(null)}
278-
/>
273+
<div key={message.id} className="msg-slide-up">
274+
<MessageBubble
275+
message={message}
276+
isTyping={typingMessageId === message.id}
277+
onTypingComplete={() => setTypingMessageId(null)}
278+
/>
279+
</div>
279280
))}
280281
{isLoading && <ThinkingIndicator />}
281282
<div ref={messagesEndRef} />
@@ -294,7 +295,7 @@ const ChatSection = () => {
294295
<div style={{ fontSize: "11px", color: "var(--term-dim)", marginBottom: "6px" }}>
295296
# quick commands:
296297
</div>
297-
<div style={{ display: "flex", flexWrap: "wrap", gap: "6px" }}>
298+
<div className="grid grid-cols-2 sm:flex sm:flex-wrap gap-1.5">
298299
{PREDEFINED_QUESTIONS.map((question, index) => (
299300
<button
300301
key={index}
@@ -364,6 +365,7 @@ const ChatSection = () => {
364365
<button
365366
onClick={() => handleSendMessage(inputValue)}
366367
disabled={isLoading || !inputValue.trim() || !isServerOnline}
368+
className="hidden sm:block"
367369
style={{
368370
background: "transparent",
369371
border: "none",

src/components/GetInTouchSection.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ const GetInTouchSection = () => {
103103

104104
{/* Two column layout */}
105105
<div
106-
style={{ display: "grid", gap: "32px" }}
107-
className="lg:grid-cols-2"
106+
style={{ display: "grid" }}
107+
className="gap-6 lg:gap-8 lg:grid-cols-2"
108108
>
109109
{/* Form */}
110110
<form onSubmit={handleSubmit}>
111-
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px", marginBottom: "12px" }}>
111+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-3">
112112
<div>
113113
<label style={labelStyle}>
114114
name <span style={{ color: "var(--term-red)" }}>*</span>

src/components/MatrixRain.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const MatrixRain = () => {
1212
const ctx = canvas.getContext("2d");
1313
if (!ctx) return;
1414

15+
// Respect reduced motion preference
16+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
17+
1518
// Lower density on mobile
1619
const isMobile = window.innerWidth < 768;
1720

0 commit comments

Comments
 (0)