feat(components): 添加鼠标交互粒子效果和高级浮动效果组件
refactor(sections): 移除各区块的多余标签元素 style(contact-section): 优化联系表单布局和动画效果
This commit is contained in:
@@ -0,0 +1,450 @@
|
||||
'use client';
|
||||
|
||||
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||
import { useMemo, useState, useEffect, useRef } from 'react';
|
||||
import { Cpu, Shield, Zap, Globe, FileText, TrendingUp, BarChart3, Users } from 'lucide-react';
|
||||
|
||||
interface FloatingOrbProps {
|
||||
size?: number;
|
||||
color?: string;
|
||||
delay?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
duration?: number;
|
||||
icon?: any;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function FloatingOrb({
|
||||
size = 80,
|
||||
color = 'rgba(196, 30, 58, 0.08)',
|
||||
delay = 0,
|
||||
x = 0,
|
||||
y = 0,
|
||||
duration = 8,
|
||||
icon: Icon,
|
||||
className = ''
|
||||
}: FloatingOrbProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className={`absolute rounded-full pointer-events-none ${className}`}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
backgroundColor: color,
|
||||
backdropFilter: 'blur(20px)',
|
||||
boxShadow: '0 0 40px rgba(196, 30, 58, 0.1)',
|
||||
}}
|
||||
initial={{ opacity: 0, scale: 0, x, y }}
|
||||
animate={{
|
||||
opacity: [0, 1, 1],
|
||||
scale: [0.5, 1, 1],
|
||||
y: [y, y - 30, y],
|
||||
x: [x, x + 15, x],
|
||||
}}
|
||||
transition={{
|
||||
duration: duration,
|
||||
delay,
|
||||
repeat: Infinity,
|
||||
ease: 'easeInOut',
|
||||
times: [0, 0.5, 1],
|
||||
}}
|
||||
>
|
||||
{Icon && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<Icon className="w-5 h-5 text-[#C41E3A]/30" />
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface FloatingLineProps {
|
||||
startX?: number;
|
||||
startY?: number;
|
||||
endX?: number;
|
||||
endY?: number;
|
||||
color?: string;
|
||||
delay?: number;
|
||||
duration?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function FloatingLine({
|
||||
startX = 0,
|
||||
startY = 0,
|
||||
endX = 200,
|
||||
endY = 0,
|
||||
color = 'rgba(28, 28, 28, 0.1)',
|
||||
delay = 0,
|
||||
duration = 6,
|
||||
className = ''
|
||||
}: FloatingLineProps) {
|
||||
return (
|
||||
<motion.svg
|
||||
className={`absolute pointer-events-none ${className}`}
|
||||
style={{
|
||||
left: startX,
|
||||
top: startY,
|
||||
width: Math.abs(endX - startX) || 100,
|
||||
height: Math.abs(endY - startY) || 2,
|
||||
overflow: 'visible',
|
||||
}}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: [0, 1, 0.5, 1] }}
|
||||
transition={{
|
||||
duration,
|
||||
delay,
|
||||
repeat: Infinity,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
>
|
||||
<motion.path
|
||||
d={`M0 0 Q${(endX - startX) / 2} ${-20 + Math.random() * 40} ${endX - startX} 0`}
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth="1"
|
||||
strokeLinecap="round"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: [0, 1, 0] }}
|
||||
transition={{
|
||||
duration: duration * 2,
|
||||
delay,
|
||||
repeat: Infinity,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
/>
|
||||
</motion.svg>
|
||||
);
|
||||
}
|
||||
|
||||
interface FloatingIconProps {
|
||||
icon: any;
|
||||
size?: number;
|
||||
color?: string;
|
||||
delay?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
rotation?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function FloatingIcon({
|
||||
icon: Icon,
|
||||
size = 24,
|
||||
color = '#1C1C1C',
|
||||
delay = 0,
|
||||
x = 0,
|
||||
y = 0,
|
||||
rotation = 0,
|
||||
className = ''
|
||||
}: FloatingIconProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className={`absolute pointer-events-none ${className}`}
|
||||
style={{
|
||||
left: x,
|
||||
top: y,
|
||||
}}
|
||||
initial={{ opacity: 0, scale: 0, rotate: rotation - 15, x, y }}
|
||||
animate={{
|
||||
opacity: [0, 1, 0.8],
|
||||
scale: [0.8, 1, 0.9],
|
||||
rotate: [rotation - 10, rotation + 10, rotation],
|
||||
y: [y, y - 25, y - 10],
|
||||
}}
|
||||
transition={{
|
||||
duration: 7 + Math.random() * 3,
|
||||
delay,
|
||||
repeat: Infinity,
|
||||
ease: 'easeInOut',
|
||||
times: [0, 0.5, 1],
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center rounded-full"
|
||||
style={{
|
||||
width: size + 24,
|
||||
height: size + 24,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.5)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(28, 28, 28, 0.08)',
|
||||
boxShadow: '0 4px 20px rgba(28, 28, 28, 0.05)',
|
||||
}}
|
||||
>
|
||||
<Icon className="w-5 h-5" style={{ color }} />
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ParticleRingProps {
|
||||
size?: number;
|
||||
color?: string;
|
||||
delay?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ParticleRing({
|
||||
size = 120,
|
||||
color = 'rgba(196, 30, 58, 0.1)',
|
||||
delay = 0,
|
||||
x = 0,
|
||||
y = 0,
|
||||
className = ''
|
||||
}: ParticleRingProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className={`absolute pointer-events-none ${className}`}
|
||||
style={{
|
||||
left: x,
|
||||
top: y,
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{
|
||||
opacity: [0, 1, 0.5],
|
||||
scale: [0.5, 1.2, 0.8],
|
||||
rotate: [0, 90, 180],
|
||||
}}
|
||||
transition={{
|
||||
duration: 12,
|
||||
delay,
|
||||
repeat: Infinity,
|
||||
ease: 'linear',
|
||||
}}
|
||||
>
|
||||
<svg width={size} height={size} viewBox="0 0 120 120">
|
||||
{[0, 60, 120, 180, 240, 300].map((angle, i) => {
|
||||
const rad = (angle * Math.PI) / 180;
|
||||
const px = 60 + Math.cos(rad) * 45;
|
||||
const py = 60 + Math.sin(rad) * 45;
|
||||
return (
|
||||
<motion.circle
|
||||
key={i}
|
||||
cx={px}
|
||||
cy={py}
|
||||
r={3}
|
||||
fill={color}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: [0.3, 1, 0.3],
|
||||
scale: [0.5, 1.5, 0.5],
|
||||
}}
|
||||
transition={{
|
||||
duration: 4,
|
||||
delay: delay + i * 0.3,
|
||||
repeat: Infinity,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<circle
|
||||
cx={60}
|
||||
cy={60}
|
||||
r={50}
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth="1"
|
||||
strokeDasharray="5 5"
|
||||
/>
|
||||
</svg>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface GlowingDotProps {
|
||||
size?: number;
|
||||
color?: string;
|
||||
delay?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function GlowingDot({
|
||||
size = 8,
|
||||
color = '#C41E3A',
|
||||
delay = 0,
|
||||
x = 0,
|
||||
y = 0,
|
||||
className = ''
|
||||
}: GlowingDotProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className={`absolute rounded-full pointer-events-none ${className}`}
|
||||
style={{
|
||||
left: x,
|
||||
top: y,
|
||||
width: size,
|
||||
height: size,
|
||||
backgroundColor: color,
|
||||
boxShadow: `0 0 ${size * 2}px ${color}`,
|
||||
}}
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{
|
||||
opacity: [0, 1, 0.5, 1],
|
||||
scale: [0.5, 1.5, 0.8, 1.2],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3 + Math.random() * 2,
|
||||
delay,
|
||||
repeat: Infinity,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface AdvancedFloatingEffectsProps {
|
||||
variant?: 'minimal' | 'balanced' | 'rich' | 'parallax';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function AdvancedFloatingEffects({
|
||||
variant = 'balanced',
|
||||
className = ''
|
||||
}: AdvancedFloatingEffectsProps) {
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { scrollY } = useScroll();
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
const config = {
|
||||
minimal: { orbs: 2, icons: 3, rings: 0, lines: 2, dots: 5 },
|
||||
balanced: { orbs: 3, icons: 5, rings: 1, lines: 4, dots: 8 },
|
||||
rich: { orbs: 5, icons: 8, rings: 2, lines: 6, dots: 12 },
|
||||
parallax: { orbs: 4, icons: 6, rings: 2, lines: 5, dots: 10 },
|
||||
};
|
||||
|
||||
const { orbs, icons, rings, lines, dots } = config[variant];
|
||||
|
||||
const iconsList = [Cpu, Shield, Zap, Globe, FileText, TrendingUp, BarChart3, Users];
|
||||
|
||||
const elements = useMemo(() => {
|
||||
if (!isMounted) return [];
|
||||
|
||||
const items = [];
|
||||
const width = typeof window !== 'undefined' ? window.innerWidth : 1920;
|
||||
const height = typeof window !== 'undefined' ? window.innerHeight : 1080;
|
||||
|
||||
for (let i = 0; i < orbs; i++) {
|
||||
items.push({
|
||||
type: 'orb',
|
||||
id: `orb-${i}`,
|
||||
props: {
|
||||
size: 60 + Math.random() * 60,
|
||||
color: i % 2 === 0 ? 'rgba(196, 30, 58, 0.08)' : 'rgba(28, 28, 28, 0.05)',
|
||||
delay: i * 0.5,
|
||||
x: width * 0.1 + (i * width * 0.35),
|
||||
y: height * 0.15 + Math.random() * height * 0.5,
|
||||
duration: 7 + Math.random() * 4,
|
||||
icon: i % 3 === 0 ? iconsList[i % iconsList.length] : undefined,
|
||||
},
|
||||
parallaxDepth: 0.1 + i * 0.1,
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < icons; i++) {
|
||||
items.push({
|
||||
type: 'icon',
|
||||
id: `icon-${i}`,
|
||||
props: {
|
||||
icon: iconsList[i % iconsList.length],
|
||||
size: 20,
|
||||
color: i % 2 === 0 ? '#C41E3A' : '#1C1C1C',
|
||||
delay: i * 0.4,
|
||||
x: width * 0.08 + (i * width * 0.12),
|
||||
y: height * 0.1 + Math.random() * height * 0.65,
|
||||
rotation: -15 + Math.random() * 30,
|
||||
},
|
||||
parallaxDepth: 0.2 + i * 0.05,
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < rings; i++) {
|
||||
items.push({
|
||||
type: 'ring',
|
||||
id: `ring-${i}`,
|
||||
props: {
|
||||
size: 100 + Math.random() * 80,
|
||||
color: i % 2 === 0 ? 'rgba(196, 30, 58, 0.1)' : 'rgba(28, 28, 28, 0.08)',
|
||||
delay: i * 0.8,
|
||||
x: width * 0.2 + (i * width * 0.4),
|
||||
y: height * 0.2 + Math.random() * height * 0.4,
|
||||
},
|
||||
parallaxDepth: 0.05 + i * 0.1,
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < lines; i++) {
|
||||
items.push({
|
||||
type: 'line',
|
||||
id: `line-${i}`,
|
||||
props: {
|
||||
startX: width * 0.05 + (i * width * 0.15),
|
||||
startY: height * 0.1 + Math.random() * height * 0.7,
|
||||
endX: width * 0.05 + (i * width * 0.15) + 80 + Math.random() * 120,
|
||||
endY: height * 0.1 + Math.random() * height * 0.7,
|
||||
color: i % 2 === 0 ? 'rgba(196, 30, 58, 0.15)' : 'rgba(28, 28, 28, 0.1)',
|
||||
delay: i * 0.6,
|
||||
duration: 5 + Math.random() * 3,
|
||||
},
|
||||
parallaxDepth: 0.15 + i * 0.05,
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < dots; i++) {
|
||||
items.push({
|
||||
type: 'dot',
|
||||
id: `dot-${i}`,
|
||||
props: {
|
||||
size: 4 + Math.random() * 6,
|
||||
color: i % 3 === 0 ? '#C41E3A' : i % 3 === 1 ? '#1C1C1C' : '#D4A574',
|
||||
delay: i * 0.3,
|
||||
x: Math.random() * width,
|
||||
y: Math.random() * height,
|
||||
},
|
||||
parallaxDepth: 0.25 + i * 0.02,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}, [orbs, icons, rings, lines, dots, isMounted, iconsList]);
|
||||
|
||||
const getParallaxStyle = (depth: number) => {
|
||||
if (variant !== 'parallax') return {};
|
||||
const y = useTransform(scrollY, [0, 500], [0, -depth * 100]);
|
||||
return { y };
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`absolute inset-0 pointer-events-none overflow-hidden ${className}`}
|
||||
>
|
||||
{elements.map((el) => {
|
||||
const parallaxStyle = getParallaxStyle(el.parallaxDepth);
|
||||
|
||||
return (
|
||||
<motion.div key={el.id} style={parallaxStyle}>
|
||||
{el.type === 'orb' && <FloatingOrb {...el.props} />}
|
||||
{el.type === 'icon' && <FloatingIcon {...el.props} />}
|
||||
{el.type === 'ring' && <ParticleRing {...el.props} />}
|
||||
{el.type === 'line' && <FloatingLine {...el.props} />}
|
||||
{el.type === 'dot' && <GlowingDot {...el.props} />}
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AdvancedFloatingEffects;
|
||||
@@ -0,0 +1,194 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState, useCallback } from 'react';
|
||||
|
||||
interface InteractiveParticle {
|
||||
x: number;
|
||||
y: number;
|
||||
originX: number;
|
||||
originY: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
size: number;
|
||||
opacity: number;
|
||||
color: string;
|
||||
life: number;
|
||||
}
|
||||
|
||||
interface MouseInteractiveParticlesProps {
|
||||
particleCount?: number;
|
||||
className?: string;
|
||||
colorScheme?: 'red' | 'dark' | 'mixed';
|
||||
interactionRadius?: number;
|
||||
}
|
||||
|
||||
export function MouseInteractiveParticles({
|
||||
particleCount = 80,
|
||||
className = '',
|
||||
colorScheme = 'mixed',
|
||||
interactionRadius = 150,
|
||||
}: MouseInteractiveParticlesProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const mouseRef = useRef({ x: -1000, y: -1000, active: false });
|
||||
const particlesRef = useRef<InteractiveParticle[]>([]);
|
||||
const animationRef = useRef<number | null>(null);
|
||||
|
||||
const getColors = useCallback(() => {
|
||||
switch (colorScheme) {
|
||||
case 'red':
|
||||
return ['#C41E3A', '#E04A68', '#A01830'];
|
||||
case 'dark':
|
||||
return ['#1C1C1C', '#2D2D2D', '#3E3E3E'];
|
||||
case 'mixed':
|
||||
default:
|
||||
return ['#C41E3A', '#1C1C1C', '#D4A574'];
|
||||
}
|
||||
}, [colorScheme]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMounted) return;
|
||||
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
|
||||
const resize = () => {
|
||||
width = window.innerWidth;
|
||||
height = window.innerHeight;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
initParticles();
|
||||
};
|
||||
|
||||
const initParticles = () => {
|
||||
const colors = getColors();
|
||||
particlesRef.current = [];
|
||||
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const x = Math.random() * width;
|
||||
const y = Math.random() * height;
|
||||
|
||||
particlesRef.current.push({
|
||||
x,
|
||||
y,
|
||||
originX: x,
|
||||
originY: y,
|
||||
vx: (Math.random() - 0.5) * 0.5,
|
||||
vy: (Math.random() - 0.5) * 0.5,
|
||||
size: Math.random() * 3 + 1,
|
||||
opacity: Math.random() * 0.5 + 0.2,
|
||||
color: colors[Math.floor(Math.random() * colors.length)],
|
||||
life: Math.random() * Math.PI * 2,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
mouseRef.current.x = e.clientX;
|
||||
mouseRef.current.y = e.clientY;
|
||||
mouseRef.current.active = true;
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
mouseRef.current.x = -1000;
|
||||
mouseRef.current.y = -1000;
|
||||
mouseRef.current.active = false;
|
||||
};
|
||||
|
||||
const animate = () => {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
particlesRef.current.forEach((particle, i) => {
|
||||
particle.life += 0.02;
|
||||
|
||||
if (mouseRef.current.active) {
|
||||
const dx = mouseRef.current.x - particle.x;
|
||||
const dy = mouseRef.current.y - particle.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < interactionRadius) {
|
||||
const force = (interactionRadius - distance) / interactionRadius;
|
||||
const angle = Math.atan2(dy, dx);
|
||||
particle.vx -= Math.cos(angle) * force * 0.5;
|
||||
particle.vy -= Math.sin(angle) * force * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
const returnForce = 0.01;
|
||||
particle.vx += (particle.originX - particle.x) * returnForce;
|
||||
particle.vy += (particle.originY - particle.y) * returnForce;
|
||||
|
||||
particle.vx *= 0.98;
|
||||
particle.vy *= 0.98;
|
||||
particle.x += particle.vx;
|
||||
particle.y += particle.vy;
|
||||
|
||||
particle.x += Math.sin(particle.life + i) * 0.1;
|
||||
particle.y += Math.cos(particle.life * 0.8 + i) * 0.1;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
|
||||
ctx.fillStyle = particle.color;
|
||||
ctx.globalAlpha = particle.opacity;
|
||||
ctx.fill();
|
||||
|
||||
particlesRef.current.forEach((otherParticle, j) => {
|
||||
if (i === j) return;
|
||||
const dx = particle.x - otherParticle.x;
|
||||
const dy = particle.y - otherParticle.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 100) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particle.x, particle.y);
|
||||
ctx.lineTo(otherParticle.x, otherParticle.y);
|
||||
ctx.strokeStyle = particle.color;
|
||||
ctx.globalAlpha = 0.05 * (1 - distance / 100);
|
||||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ctx.globalAlpha = 1;
|
||||
animationRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
resize();
|
||||
initParticles();
|
||||
animate();
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
window.addEventListener('mousemove', handleMouseMove);
|
||||
window.addEventListener('mouseleave', handleMouseLeave);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', resize);
|
||||
window.removeEventListener('mousemove', handleMouseMove);
|
||||
window.removeEventListener('mouseleave', handleMouseLeave);
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
};
|
||||
}, [isMounted, particleCount, getColors, interactionRadius]);
|
||||
|
||||
if (!isMounted) return null;
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className={`absolute inset-0 pointer-events-none ${className}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default MouseInteractiveParticles;
|
||||
@@ -53,9 +53,6 @@ export function AboutSection() {
|
||||
className="max-w-4xl mx-auto"
|
||||
>
|
||||
<div className="text-center mb-16">
|
||||
<span className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full border border-[#1C1C1C]/20 bg-[#F5F5F5] text-[#1C1C1C] text-sm font-medium mb-4">
|
||||
关于我们
|
||||
</span>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
|
||||
关于 <span className="text-[#C41E3A]">{COMPANY_INFO.shortName}</span>
|
||||
</h2>
|
||||
|
||||
@@ -130,7 +130,7 @@ export function ContactSection() {
|
||||
<div className="grid lg:grid-cols-5 gap-12 lg:gap-16">
|
||||
<div
|
||||
className={`
|
||||
lg:col-span-2 space-y-8
|
||||
lg:col-span-2 space-y-8 flex flex-col
|
||||
opacity-0 translate-y-4
|
||||
${isVisible ? 'animate-fade-in-up stagger-1' : ''}
|
||||
`}
|
||||
@@ -209,16 +209,16 @@ export function ContactSection() {
|
||||
|
||||
<div
|
||||
className={`
|
||||
lg:col-span-3
|
||||
lg:col-span-3 flex flex-col
|
||||
opacity-0 translate-y-4
|
||||
${isVisible ? 'animate-fade-in-up stagger-2' : ''}
|
||||
`}
|
||||
>
|
||||
<div className="bg-[#F5F7FA] p-6 sm:p-8 rounded-lg border border-[#E2E8F0]">
|
||||
<div className="bg-[#F5F7FA] p-6 sm:p-8 rounded-lg border border-[#E2E8F0] flex-1 flex flex-col">
|
||||
<h3 className="text-lg font-semibold text-[#1A1A2E] mb-6">发送消息</h3>
|
||||
|
||||
{isSubmitted ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-center py-12 flex-1 flex items-center justify-center">
|
||||
<div className="w-16 h-16 bg-[#C41E3A] rounded-full flex items-center justify-center mx-auto mb-4 animate-stamp-in">
|
||||
<CheckCircle2 className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
@@ -226,7 +226,7 @@ export function ContactSection() {
|
||||
<p className="text-[#718096]">感谢您的留言,我们会尽快与您联系!</p>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<form onSubmit={handleSubmit} className="space-y-5 flex-1 flex flex-col">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<Input
|
||||
label="姓名"
|
||||
@@ -275,7 +275,7 @@ export function ContactSection() {
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
className="w-full group"
|
||||
className="w-full group mt-auto"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
|
||||
@@ -20,9 +20,6 @@ export function NewsSection() {
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center max-w-3xl mx-auto mb-16"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2 px-4 py-2 rounded-full border border-[#1C1C1C]/20 bg-[#F5F5F5] text-[#1C1C1C] text-sm font-medium mb-4">
|
||||
新闻动态
|
||||
</span>
|
||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-[#1C1C1C] mb-6">
|
||||
最新<span className="text-[#C41E3A]">资讯</span>
|
||||
</h2>
|
||||
|
||||
@@ -24,9 +24,6 @@ export function ProductsSection() {
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center max-w-3xl mx-auto mb-16"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full border border-[#1C1C1C]/20 bg-[#F5F5F5] text-[#1C1C1C] text-sm font-medium mb-4">
|
||||
产品服务
|
||||
</span>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
|
||||
我们的<span className="text-[#C41E3A]">产品</span>
|
||||
</h2>
|
||||
|
||||
@@ -31,9 +31,6 @@ export function ServicesSection() {
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center max-w-3xl mx-auto mb-16"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full border border-[#1C1C1C]/20 bg-[#F5F5F5] text-[#1C1C1C] text-sm font-medium mb-4">
|
||||
核心业务
|
||||
</span>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
|
||||
我们的 <span className="text-[#C41E3A]">核心服务</span>
|
||||
</h2>
|
||||
|
||||
Reference in New Issue
Block a user