Files
novalon-website/src/components/sections/products-section.tsx
T
张翔 e83ecddfe5 refactor(project): 全面清理项目代码并重命名项目
- 移除无用文件和空文件夹,清理 effects 和 scripts 目录
- 将项目从 ruixin-website-react 重命名为 novalon-website-react
- 修复所有测试用例,确保 731 个测试全部通过
- 优化组件导入路径和测试 mock 设置
- 更新项目配置文件和依赖管理

关联任务:项目清理与重构
2026-04-27 12:56:22 +08:00

135 lines
6.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 } from 'framer-motion';
import { useInView } from 'framer-motion';
import { useRef } from 'react';
import { StaticLink } from '@/components/ui/static-link';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { RippleButton } from '@/components/ui/ripple-button';
import { InkCard } from '@/lib/animations';
import { Badge } from '@/components/ui/badge';
import { ArrowRight, Check, TrendingUp } from 'lucide-react';
import { PRODUCTS } from '@/lib/constants';
export function ProductsSection() {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
return (
<section id="products" role="region" aria-labelledby="products-heading" className="py-24 bg-[#F5F7FA] relative overflow-hidden" ref={ref}>
<div className="absolute top-1/2 left-0 w-[400px] h-[400px] bg-[rgba(79,70,229,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">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6 }}
className="text-left max-w-3xl mx-auto mb-16"
>
<div className="w-16 h-1 bg-[#C41E3A] rounded-full mb-6" />
<h2 id="products-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
<span className="text-[#C41E3A] font-calligraphy"></span>
</h2>
<p className="text-lg text-[#5C5C5C]">
</p>
</motion.div>
{PRODUCTS.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{PRODUCTS.map((product) => (
<InkCard
key={product.id}
className="group cursor-pointer rounded-xl border border-[#E5E5E5] bg-white p-0 overflow-hidden hover:border-[#C41E3A] transition-colors"
>
<StaticLink href={`/products/${product.id}`}>
<Card className="h-full flex flex-col border-0 shadow-none bg-transparent">
<CardHeader>
<Badge variant="secondary" className="w-fit mb-3">
{product.category}
</Badge>
<CardTitle>{product.title}</CardTitle>
</CardHeader>
<CardContent className="flex-1 flex flex-col">
<CardDescription className="text-base leading-relaxed mb-4 flex-1">
{product.description}
</CardDescription>
<div className="mb-4">
<p className="text-sm font-medium text-[#1C1C1C] mb-2"></p>
<div className="flex flex-wrap gap-1.5">
{product.features.slice(0, 4).map((feature, idx) => (
<span
key={idx}
className="inline-flex items-center text-xs px-2 py-1 bg-[#FAFAFA] text-[#3D3D3D] rounded border border-[#E5E5E5]"
>
<Check className="w-3 h-3 mr-1 text-[#C41E3A]" />
{feature}
</span>
))}
</div>
</div>
<div className="mb-4">
<p className="text-sm font-medium text-[#1C1C1C] mb-2 flex items-center">
<TrendingUp className="w-4 h-4 mr-1 text-[#C41E3A]" />
</p>
<ul className="space-y-1">
{product.benefits.map((benefit, idx) => (
<li key={idx} className="text-xs text-[#5C5C5C] flex items-start">
<span className="text-[#C41E3A] mr-1.5"></span>
{benefit}
</li>
))}
</ul>
</div>
<div className="w-full mt-auto px-4 py-2 text-center text-sm font-medium border border-[#E5E5E5] rounded-md group-hover:bg-[#A01830] group-hover:text-white group-hover:border-[#A01830] transition-colors">
<ArrowRight className="ml-2 w-4 h-4 inline" />
</div>
</CardContent>
</Card>
</StaticLink>
</InkCard>
))}
</div>
) : (
<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.5 }}
className="mt-20 text-center"
>
<div className="bg-white rounded-2xl p-12 border border-[#E2E8F0] relative overflow-hidden">
<div className="absolute inset-0 pointer-events-none">
<div className="absolute top-0 right-0 w-64 h-64 bg-[rgba(79,70,229,0.03)] rounded-full blur-3xl" />
<div className="absolute bottom-0 left-0 w-48 h-48 bg-[rgba(196,30,58,0.02)] rounded-full blur-3xl" />
</div>
<div className="relative z-10">
<h3 className="text-2xl sm:text-3xl font-bold text-[#1A1A2E] mb-4">
</h3>
<p className="text-[#718096] mb-8 max-w-2xl mx-auto">
</p>
<StaticLink href="/contact">
<RippleButton className="inline-flex items-center gap-2 px-6 py-2.5 bg-[#C41E3A] hover:bg-[#A01830] text-white rounded-lg text-sm font-medium transition-colors">
<ArrowRight className="w-4 h-4" />
</RippleButton>
</StaticLink>
</div>
</div>
</motion.div>
</div>
</section>
);
}