chore: sync marketing pages, CMS extensions, tests and project docs
同步工作区剩余变更,主要包括: - 营销页面组件与布局持续优化(about/news/services/solutions/team 等) - 详情页四层叙事组件、布局组件、UI 组件调整 - CMS 数据模型、API 路由、权限、工作流、站内通知、媒体管理扩展 - 新增/补充单元测试与 E2E 测试(cms-workflow.spec.ts 等) - ESLint 9 迁移、jest/tsconfig 配置更新、依赖调整 - 新增 ADR、CMS 评估文档、Release Review / Acceptance 报告 - 移除水墨装饰组件与大体积未使用字体文件
This commit is contained in:
@@ -10,8 +10,6 @@ import {
|
||||
TrendingUp,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react';
|
||||
import { TiltCard, InkGlowCard } from './micro-interactions';
|
||||
import { SectionHeader, InkDivider } from './brand-elements';
|
||||
import type { Service } from '@/lib/constants/services';
|
||||
|
||||
interface ServiceValueSectionProps {
|
||||
@@ -22,164 +20,146 @@ export function ServiceValueSection({ service }: ServiceValueSectionProps) {
|
||||
const icons: LucideIcon[] = [Zap, Clock, Users, Award, TrendingUp];
|
||||
|
||||
return (
|
||||
<section className="relative bg-white py-24 lg:py-32 overflow-hidden">
|
||||
<div className="container-wide relative">
|
||||
<SectionHeader
|
||||
title={`为什么选择我们的${service.title}?`}
|
||||
subtitle={service.overview}
|
||||
/>
|
||||
<section className="relative bg-white py-20 sm:py-28 md:py-36 overflow-hidden">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 32 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-120px' }}
|
||||
transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="mb-16 sm:mb-20"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-px bg-brand" />
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
服务能力
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[0.95] max-w-3xl">
|
||||
为什么选择我们的{service.title}?
|
||||
</h2>
|
||||
<p className="text-lg text-text-secondary max-w-2xl mt-6 leading-relaxed">
|
||||
{service.overview}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="mt-16">
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-[#C41E3A] to-[#99182d] flex items-center justify-center shadow-md shadow-brand/20">
|
||||
<Zap className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-ink">核心能力</h3>
|
||||
<p className="text-sm text-text-muted mt-0.5">我们提供的服务特色</p>
|
||||
</div>
|
||||
{/* 核心能力 */}
|
||||
<div className="mb-16">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-px bg-brand" />
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
核心能力
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
{service.features.map((feature, index) => {
|
||||
const Icon = icons[index % icons.length]!;
|
||||
|
||||
return (
|
||||
<InkGlowCard
|
||||
<motion.div
|
||||
key={feature}
|
||||
active={index === 0}
|
||||
className="group p-6 hover:border-brand/20"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: index * 0.06,
|
||||
duration: 0.5,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="bain-card p-6"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: index * 0.08,
|
||||
duration: 0.5,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div
|
||||
className={`shrink-0 w-11 h-11 rounded-xl flex items-center justify-center transition-all duration-300 shadow-sm ${
|
||||
index === 0
|
||||
? 'bg-gradient-to-br from-[#C41E3A] to-[#99182d] text-white'
|
||||
: 'bg-bg-secondary text-text-secondary group-hover:from-[#C41E3A] group-hover:to-[#99182d] group-hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-5.5 h-5.5" />
|
||||
</div>
|
||||
<p className="text-sm font-medium text-text-secondary leading-relaxed pt-2">
|
||||
{feature.split(':')[1] || feature}
|
||||
</p>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`shrink-0 w-11 h-11 flex items-center justify-center ${
|
||||
index === 0
|
||||
? 'bg-brand text-white'
|
||||
: 'bg-bg-secondary text-text-secondary'
|
||||
}`}>
|
||||
<Icon className="w-5 h-5" />
|
||||
</div>
|
||||
</motion.div>
|
||||
</InkGlowCard>
|
||||
<p className="text-sm font-medium text-text-secondary leading-relaxed pt-2">
|
||||
{feature.split(':')[1] || feature}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 32 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.75, delay: 0.3 }}
|
||||
className="mt-16"
|
||||
>
|
||||
<InkDivider variant="subtle" />
|
||||
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-green-500 to-emerald-600 flex items-center justify-center shadow-md shadow-green-500/20">
|
||||
<Award className="w-6 h-6 text-white" />
|
||||
{/* 服务优势 + 服务流程:不对称布局 */}
|
||||
<div className="asymmetric-wide-narrow">
|
||||
{/* 服务优势 */}
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-px bg-brand" />
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
服务优势
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-ink">服务优势</h3>
|
||||
<p className="text-sm text-text-muted mt-0.5">选择我们的理由</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
{service.benefits.map((benefit, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, x: index % 2 === 0 ? -20 : 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: 0.4 + index * 0.06,
|
||||
duration: 0.5,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
}}
|
||||
className="group p-6 rounded-2xl bg-bg-secondary border border-border-primary hover:border-brand/30 hover:shadow-md hover:-translate-y-1 transition-all duration-300"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-600 mt-0.5 shrink-0 group-hover:scale-110 transition-transform" />
|
||||
<div className="space-y-4">
|
||||
{service.benefits.map((benefit, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: index * 0.06,
|
||||
duration: 0.4,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="bain-card p-5 flex items-start gap-3"
|
||||
>
|
||||
<CheckCircle2 className="w-5 h-5 text-brand mt-0.5 shrink-0" />
|
||||
<p className="text-sm font-medium text-text-secondary leading-relaxed">{benefit}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{service.process && service.process.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 32 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.75, delay: 0.45 }}
|
||||
className="mt-20"
|
||||
>
|
||||
<InkDivider variant="decorative" />
|
||||
|
||||
<div className="text-center mb-12">
|
||||
<h3 className="text-2xl md:text-3xl font-bold tracking-tight text-ink">
|
||||
服务流程
|
||||
</h3>
|
||||
<p className="text-text-secondary mt-3">标准化流程保障服务质量</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute left-8 top-0 bottom-0 w-0.5 bg-gradient-to-b from-[#C41E3A] via-blue-400 to-green-400 hidden md:block" />
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 服务流程 */}
|
||||
{service.process && service.process.length > 0 && (
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-px bg-brand" />
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
服务流程
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{service.process.map((step, index) => (
|
||||
<motion.div
|
||||
key={step}
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
delay: 0.5 + index * 0.1,
|
||||
duration: 0.55,
|
||||
ease: [0.22, 1, 0.36, 1] as const,
|
||||
delay: index * 0.08,
|
||||
duration: 0.45,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="relative flex gap-6 group"
|
||||
className="bain-card p-5"
|
||||
>
|
||||
<div className="relative z-10 shrink-0 w-16 flex justify-center">
|
||||
<div className="w-4 h-4 rounded-full bg-brand group-hover:scale-110 transition-transform duration-300" />
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-6 h-6 rounded-full bg-brand text-white flex items-center justify-center shrink-0 mt-0.5">
|
||||
<span className="text-xs font-bold">{index + 1}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-ink text-sm mb-1">
|
||||
{step.split(':')[0]}
|
||||
</h4>
|
||||
<p className="text-xs text-text-secondary leading-relaxed">
|
||||
{step.split(':')[1] || step}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TiltCard
|
||||
tiltAmount={5}
|
||||
glareEnable={false}
|
||||
className="flex-1 p-6 rounded-2xl bg-white border border-border-primary hover:border-brand/30 hover:shadow-md transition-all"
|
||||
>
|
||||
<h4 className="font-bold text-ink mb-2">
|
||||
{step.split(':')[0]}
|
||||
</h4>
|
||||
<p className="text-sm text-text-secondary leading-relaxed">
|
||||
{step.split(':')[1] || step}
|
||||
</p>
|
||||
</TiltCard>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user