feat: integrate enhanced seal animation in hero section
This commit is contained in:
@@ -1,11 +1,53 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { CTAButton } from '@/components/ui/cta-button';
|
||||
import { AnimatedNumber } from '@/components/ui/animated-number';
|
||||
import { SealParticle } from '@/components/effects/seal-particle';
|
||||
import { SealAnimationEnhanced } from '@/components/effects/seal-animation-enhanced';
|
||||
import { StampAnimation } from '@/components/effects/stamp-animation';
|
||||
import { COMPANY_INFO, STATS } from '@/lib/constants';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { ArrowRight, Sparkles } from 'lucide-react';
|
||||
|
||||
export function HeroSection() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
const statsRef = useRef<HTMLDivElement>(null);
|
||||
const [statsVisible, setStatsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsVisible(true);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
if (sectionRef.current) {
|
||||
observer.observe(sectionRef.current);
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!statsRef.current) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setStatsVisible(true);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 }
|
||||
);
|
||||
|
||||
observer.observe(statsRef.current);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const handleScrollTo = (id: string) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
@@ -14,110 +56,169 @@ export function HeroSection() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="home" className="relative min-h-screen flex items-center overflow-hidden pt-20">
|
||||
{/* Background Gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-[#FEF2F4]/50 via-white to-[#FAF8F8]" />
|
||||
|
||||
{/* Decorative Elements */}
|
||||
<div className="absolute top-1/4 right-0 w-96 h-96 bg-[#C41E3A]/5 rounded-full blur-3xl" />
|
||||
<div className="absolute bottom-1/4 left-0 w-72 h-72 bg-[#C41E3A]/3 rounded-full blur-3xl" />
|
||||
<section
|
||||
id="home"
|
||||
ref={sectionRef}
|
||||
className="relative min-h-screen flex items-center pt-16 overflow-hidden"
|
||||
>
|
||||
<SealParticle
|
||||
particleCount={30}
|
||||
colors={['#C41E3A', '#D4A574', '#8B4513']}
|
||||
minSize={1}
|
||||
maxSize={4}
|
||||
speed={0.3}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div className="container-custom relative z-10 py-20">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
{/* Badge */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
{/* 增强版印章动画 - 桌面端显示 */}
|
||||
<div className="absolute right-0 top-1/2 -translate-y-1/2 hidden lg:block z-10">
|
||||
<SealAnimationEnhanced
|
||||
width={400}
|
||||
height={400}
|
||||
particleCount={150}
|
||||
colors={['#C41E3A', '#D4A574', '#8B4513']}
|
||||
sealText="睿新"
|
||||
animationStages={true}
|
||||
onStageChange={(stage) => console.log('Animation stage:', stage)}
|
||||
className="opacity-80"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<div className="absolute top-20 right-0 w-96 h-96 bg-gradient-radial from-[#C41E3A]/5 to-transparent rounded-full blur-3xl" />
|
||||
<div className="absolute bottom-20 left-0 w-80 h-80 bg-gradient-radial from-[#171717]/3 to-transparent rounded-full blur-3xl" />
|
||||
</div>
|
||||
|
||||
<div className="container-wide py-24 md:py-32 lg:py-40 relative z-10">
|
||||
<div className="max-w-3xl">
|
||||
<div
|
||||
className={`
|
||||
opacity-0 translate-y-4
|
||||
${isVisible ? 'animate-fade-in-up' : ''}
|
||||
`}
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full bg-white border border-[#E8E0E0] shadow-sm mb-8">
|
||||
<span className="relative flex h-2.5 w-2.5">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#C41E3A] opacity-75" />
|
||||
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-[#C41E3A]" />
|
||||
</span>
|
||||
<span className="text-sm font-medium text-[#4A4A4A] tracking-wide">
|
||||
专业科技服务提供商
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<StampAnimation delay={300}>
|
||||
<div className="relative">
|
||||
<div className="w-3 h-3 bg-[#C41E3A] rounded-sm rotate-45" />
|
||||
<div className="absolute inset-0 w-3 h-3 bg-[#C41E3A] rounded-sm rotate-45 animate-ping opacity-30" />
|
||||
</div>
|
||||
</StampAnimation>
|
||||
<span className="text-sm text-[#525252] tracking-wide font-medium">
|
||||
印章文化 × 现代科技
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Main Title */}
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="text-4xl sm:text-5xl lg:text-6xl font-bold text-[#1A1A1A] leading-tight mb-6"
|
||||
<h1
|
||||
className={`
|
||||
text-4xl sm:text-5xl lg:text-6xl font-semibold
|
||||
text-[#171717] leading-[1.1] tracking-tight mb-6
|
||||
opacity-0 translate-y-4
|
||||
${isVisible ? 'animate-fade-in-up stagger-1' : ''}
|
||||
`}
|
||||
>
|
||||
{COMPANY_INFO.name}
|
||||
</motion.h1>
|
||||
<StampAnimation delay={500}>
|
||||
<span className="inline-block">{COMPANY_INFO.name}</span>
|
||||
</StampAnimation>
|
||||
</h1>
|
||||
|
||||
{/* Slogan */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="text-xl sm:text-2xl text-[#C41E3A] font-medium mb-8"
|
||||
<p
|
||||
className={`
|
||||
text-xl sm:text-2xl text-[#525252] mb-8
|
||||
opacity-0 translate-y-4
|
||||
${isVisible ? 'animate-fade-in-up stagger-2' : ''}
|
||||
`}
|
||||
>
|
||||
{COMPANY_INFO.slogan}
|
||||
</motion.p>
|
||||
<span className="text-[#C41E3A] font-medium">{COMPANY_INFO.slogan.split(',')[0]}</span>
|
||||
,{COMPANY_INFO.slogan.split(',')[1]}
|
||||
</p>
|
||||
|
||||
{/* Description */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
className="text-lg text-[#4A4A4A] max-w-2xl mx-auto mb-10"
|
||||
<p
|
||||
className={`
|
||||
text-base sm:text-lg text-[#737373] max-w-2xl mb-12
|
||||
leading-relaxed
|
||||
opacity-0 translate-y-4
|
||||
${isVisible ? 'animate-fade-in-up stagger-3' : ''}
|
||||
`}
|
||||
>
|
||||
{COMPANY_INFO.description}
|
||||
</motion.p>
|
||||
</p>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="flex flex-col sm:flex-row items-center justify-center gap-4"
|
||||
<div
|
||||
className={`
|
||||
flex flex-col sm:flex-row items-start sm:items-center gap-4 mb-20
|
||||
opacity-0 translate-y-4
|
||||
${isVisible ? 'animate-fade-in-up stagger-4' : ''}
|
||||
`}
|
||||
>
|
||||
<Button
|
||||
<CTAButton
|
||||
size="lg"
|
||||
onClick={() => handleScrollTo('about')}
|
||||
className="group"
|
||||
icon={<ArrowRight className="w-4 h-4" />}
|
||||
iconPosition="right"
|
||||
>
|
||||
了解更多
|
||||
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1" />
|
||||
</Button>
|
||||
<Button
|
||||
</CTAButton>
|
||||
<CTAButton
|
||||
size="lg"
|
||||
variant="outline"
|
||||
variant="outline"
|
||||
onClick={() => handleScrollTo('contact')}
|
||||
icon={<Sparkles className="w-4 h-4" />}
|
||||
iconPosition="left"
|
||||
>
|
||||
联系我们
|
||||
</Button>
|
||||
</motion.div>
|
||||
</CTAButton>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.5 }}
|
||||
className="mt-20 grid grid-cols-2 md:grid-cols-4 gap-8"
|
||||
<div
|
||||
ref={statsRef}
|
||||
className={`
|
||||
pt-12 border-t border-[#E5E5E5]/50
|
||||
opacity-0 translate-y-4
|
||||
${isVisible ? 'animate-fade-in-up stagger-5' : ''}
|
||||
`}
|
||||
>
|
||||
{STATS.map((stat, index) => (
|
||||
<motion.div
|
||||
key={stat.label}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.6 + index * 0.1 }}
|
||||
className="text-center"
|
||||
>
|
||||
<div className="text-3xl sm:text-4xl font-bold text-[#C41E3A]">{stat.value}</div>
|
||||
<div className="text-sm text-[#6B6B6B] mt-1">{stat.label}</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-12">
|
||||
{STATS.map((stat, index) => (
|
||||
<StatItem
|
||||
key={stat.label}
|
||||
stat={stat}
|
||||
index={index}
|
||||
shouldAnimate={statsVisible}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute right-0 top-1/2 -translate-y-1/2 w-1/3 h-px bg-gradient-to-l from-[#C41E3A]/10 to-transparent hidden lg:block" />
|
||||
<div className="absolute left-0 bottom-20 w-1/4 h-px bg-gradient-to-r from-[#171717]/5 to-transparent hidden lg:block" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function StatItem({ stat, index, shouldAnimate }: {
|
||||
stat: { value: string; label: string };
|
||||
index: number;
|
||||
shouldAnimate: boolean;
|
||||
}) {
|
||||
const numericValue = parseInt(stat.value.replace(/\D/g, ''));
|
||||
const suffix = stat.value.replace(/[\d]/g, '');
|
||||
|
||||
return (
|
||||
<div className="group cursor-default">
|
||||
<div className="text-2xl sm:text-3xl font-semibold text-[#C41E3A] mb-1 transition-all duration-300 group-hover:translate-x-0.5">
|
||||
<AnimatedNumber
|
||||
value={numericValue}
|
||||
suffix={suffix}
|
||||
duration={2000}
|
||||
trigger={shouldAnimate}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-[#737373] transition-colors duration-200 group-hover:text-[#525252]">
|
||||
{stat.label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user