Files
novalon-website/src/components/sections/services-section.tsx
T
张翔 6ae0f1274f refactor: 页面组件优化与墨韵分割线组件化
- 提取 AnimatedInkDivider 组件替代硬编码 ink-divider div
- 重构各营销页面组件代码结构优化
- 修正统计数据:自研产品 4 -> 6
- 更新 about 页面测试用例
2026-04-29 19:15:58 +08:00

92 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';
import { StaticLink } from '@/components/ui/static-link';
import { Code, BarChart3, Lightbulb, Puzzle, ArrowRight } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { RippleButton } from '@/components/ui/ripple-button';
import { InkCard, InkReveal, StaggerContainer, StaggerItem } from '@/lib/animations';
import { SERVICES } from '@/lib/constants';
const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
Code,
BarChart3,
Lightbulb,
Puzzle,
};
export function ServicesSection() {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
return (
<section id="services" aria-labelledby="services-heading" className="py-24 bg-white relative overflow-hidden" ref={ref}>
<div className="absolute top-1/3 left-0 w-[400px] h-[400px] bg-[rgba(196,30,58,0.03)] rounded-full blur-3xl" />
<div className="absolute top-1/3 right-0 w-[300px] h-[300px] bg-[rgba(196,30,58,0.02)] rounded-full blur-3xl" />
<div className="container-wide relative z-10">
{/* 标题 - InkReveal 墨迹揭示 */}
<InkReveal className="text-center max-w-3xl mx-auto mb-16">
<div className="w-16 h-1 bg-[var(--color-brand-primary)] rounded-full mb-6" />
<h2 id="services-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-4">
<span className="text-[var(--color-brand-primary)] font-calligraphy"></span>
</h2>
<p className="text-lg text-[#5C5C5C] max-w-2xl">
</p>
</InkReveal>
{SERVICES.length > 0 ? (
<StaggerContainer className="grid grid-cols-2 md:grid-cols-2 lg:grid-cols-4 gap-6" staggerDelay={0.12}>
{SERVICES.map((service) => {
const Icon = iconMap[service.icon];
return (
<StaggerItem key={service.id}>
<InkCard
className="rounded-xl border border-[#E5E5E5] bg-white p-6 hover:border-[var(--color-brand-primary)] transition-colors"
>
<StaticLink href={`/services/${service.id}`}>
<Card className="p-0 h-full border-0 shadow-none bg-transparent group cursor-pointer">
<CardContent className="p-0">
<div className="w-12 h-12 rounded-xl bg-[#F5F5F5] flex items-center justify-center mb-4 group-hover:bg-[var(--color-brand-primary)] transition-all duration-300">
{Icon && <Icon className="w-6 h-6 text-[#1C1C1C] group-hover:text-white transition-colors" />}
</div>
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-3 group-hover:text-[var(--color-brand-primary)] transition-colors">{service.title}</h3>
<p className="text-[#5C5C5C] text-sm leading-relaxed">{service.description}</p>
<div className="mt-4 flex items-center text-[var(--color-brand-primary)] text-sm font-medium opacity-0 md:group-hover:opacity-100 md:opacity-0 transition-opacity">
<ArrowRight className="ml-1 w-4 h-4" />
</div>
</CardContent>
</Card>
</StaticLink>
</InkCard>
</StaggerItem>
);
})}
</StaggerContainer>
) : (
<div className="text-center py-12">
<p className="text-lg text-[#5C5C5C]"></p>
</div>
)}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.4 }}
className="text-center mt-12"
>
<StaticLink href="/services">
<RippleButton className="inline-flex items-center gap-2 px-6 py-2.5 border border-[#E5E5E5] rounded-lg text-sm font-medium text-[#1C1C1C] hover:border-[var(--color-brand-primary)] hover:text-[var(--color-brand-primary)] transition-colors">
<ArrowRight className="w-4 h-4" />
</RippleButton>
</StaticLink>
</motion.div>
</div>
</section>
);
}