feat(content): deepen Bain differentiation across product, solution and service pages

- Replace decorative numbering and side-stripe borders with structured copy
- Add scenario positioning, capability metrics and verifiable dimensions
- Refine service timelines, deliverables and case-study narratives
- Update content constants to support CMS seeding
This commit is contained in:
张翔
2026-07-08 14:46:51 +08:00
parent f4b98fb730
commit 7f36211342
22 changed files with 601 additions and 497 deletions
+59 -10
View File
@@ -26,6 +26,11 @@ export interface CaseDetailData {
timeline: { phase: string; duration: string; description: string }[];
services: { id: string; title: string; description: string }[];
testimonial?: { quote: string; author: string; role: string };
projectDuration?: string;
departments?: string[];
dataScale?: string;
businessProblem?: string;
verified?: boolean;
}
interface CaseDetailHeroProps {
@@ -60,7 +65,7 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
transition={{ duration: 0.6, ease: EASE }}
>
<span
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-[11px] font-medium tracking-wider uppercase"
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-xs font-medium"
style={{
backgroundColor: 'var(--color-brand-soft)',
color: 'var(--color-brand)',
@@ -136,11 +141,38 @@ function CaseDetailHero({ data }: CaseDetailHeroProps) {
))}
</motion.div>
{/* Verifiable dimensions */}
<motion.div
className="flex flex-wrap gap-x-6 gap-y-3 text-sm mb-10 border-y border-border-primary py-4"
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.65, ease: EASE }}
>
{data.projectDuration && (
<div style={{ color: 'var(--color-text-secondary)' }}>
<span style={{ color: 'var(--color-text-muted)' }}>项目周期</span> {data.projectDuration}
</div>
)}
{data.departments && data.departments.length > 0 && (
<div style={{ color: 'var(--color-text-secondary)' }}>
<span style={{ color: 'var(--color-text-muted)' }}>涉及部门</span> {data.departments.join('、')}
</div>
)}
{data.dataScale && (
<div style={{ color: 'var(--color-text-secondary)' }}>
<span style={{ color: 'var(--color-text-muted)' }}>数据规模</span> {data.dataScale}
</div>
)}
{data.verified && (
<div style={{ color: 'var(--color-brand)' }}>✓ 客户授权公开</div>
)}
</motion.div>
<motion.div
className="flex flex-wrap gap-4"
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.7, ease: EASE }}
transition={{ duration: 0.8, delay: 0.8, ease: EASE }}
>
<Button size="lg" asChild>
<a href="/contact">
@@ -172,6 +204,25 @@ function ChallengeSection({ data }: { data: CaseDetailData }) {
</ScrollReveal>
<ScrollReveal delay={0.1} className="lg:col-span-7">
{data.businessProblem && (
<div
className="mb-6 pl-4 border-l-2"
style={{ borderColor: 'var(--color-brand)' }}
>
<div
className="text-xs font-medium mb-2"
style={{ color: 'var(--color-text-muted)' }}
>
具体业务问题
</div>
<p
className="text-base md:text-lg leading-relaxed"
style={{ color: 'var(--color-text-primary)' }}
>
{data.businessProblem}
</p>
</div>
)}
<div
className="p-8 sm:p-10 lg:p-12"
style={{
@@ -181,7 +232,7 @@ function ChallengeSection({ data }: { data: CaseDetailData }) {
<div className="flex items-center gap-3 mb-6">
<Target className="w-6 h-6" style={{ color: 'var(--color-brand)' }} />
<span
className="text-sm font-semibold tracking-wider uppercase"
className="text-sm font-semibold"
style={{ color: 'var(--color-brand)' }}
>
面临的挑战
@@ -224,7 +275,7 @@ function SolutionSection({ data }: { data: CaseDetailData }) {
<div className="flex items-center gap-3 mb-6">
<Lightbulb className="w-6 h-6" style={{ color: 'var(--color-brand)' }} />
<span
className="text-sm font-semibold tracking-wider uppercase"
className="text-sm font-semibold"
style={{ color: 'var(--color-brand)' }}
>
核心思路
@@ -240,7 +291,7 @@ function SolutionSection({ data }: { data: CaseDetailData }) {
<div className="flex items-center gap-3 mb-6">
<TrendingUp className="w-6 h-6" style={{ color: 'var(--color-brand)' }} />
<span
className="text-sm font-semibold tracking-wider uppercase"
className="text-sm font-semibold"
style={{ color: 'var(--color-brand)' }}
>
实施路径
@@ -336,10 +387,10 @@ function ResultsSection({ data }: { data: CaseDetailData }) {
<div className="max-w-[1280px] mx-auto px-5 sm:px-8 lg:px-16 relative z-10">
<ScrollReveal className="mb-16 sm:mb-20 text-center max-w-3xl mx-auto">
<div
className="text-[10px] tracking-[4px] uppercase font-medium mb-4"
className="text-xs font-medium mb-4"
style={{ color: 'rgba(255,255,255,0.6)' }}
>
Results
项目成果
</div>
<h2 className="font-sans text-3xl sm:text-4xl md:text-5xl font-bold text-white tracking-tight leading-tight mb-6">
可量化的业务成果
@@ -431,14 +482,12 @@ function RelatedServicesSection({ services }: { services: CaseDetailData['servic
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
<StaggerReveal staggerDelay={0.08}>
{services.slice(0, 3).map((s, i) => (
{services.slice(0, 3).map((s) => (
<ServiceCard
key={s.id}
number={String(i + 1).padStart(2, '0')}
title={s.title}
description={s.description}
href={`/services/${s.id}`}
color="brand"
/>
))}
</StaggerReveal>
+5 -41
View File
@@ -8,7 +8,7 @@ import { ArrowRight } from 'lucide-react';
export type ServiceColor = 'brand' | 'blue' | 'teal' | 'amber' | 'purple';
interface ServiceCardProps {
number: string;
number?: string;
title: string;
description: string;
color?: ServiceColor;
@@ -17,62 +17,26 @@ interface ServiceCardProps {
delay?: number;
}
const colorMap: Record<ServiceColor, string> = {
brand: 'var(--color-brand)',
blue: 'var(--color-accent-blue)',
teal: 'var(--color-accent-teal)',
amber: 'var(--color-accent-amber)',
purple: 'var(--color-accent-purple)',
};
export function ServiceCard({
number,
number: _number,
title,
description,
color = 'brand',
color: _color = 'brand',
href,
className = '',
delay = 0,
}: ServiceCardProps) {
const shouldReduceMotion = useReducedMotion();
const accentColor = colorMap[color];
return (
<motion.div
className={`group relative bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)] rounded-[var(--radius-lg)] overflow-hidden transition-all duration-[var(--transition-normal)] ease-[var(--ease-ink)] hover:-translate-y-1 hover:shadow-[var(--shadow-lg)] hover:border-[var(--color-brand)]/30 ${className}`}
className={`group relative bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)] rounded-[var(--radius-lg)] overflow-hidden transition-all duration-[var(--transition-normal)] ease-[var(--ease-ink)] hover:-translate-y-1 hover:shadow-[var(--shadow-lg)] hover:border-[var(--color-brand)]/20 ${className}`}
initial={shouldReduceMotion ? {} : { opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-40px' }}
transition={{ duration: 0.6, delay, ease: [0.22, 1, 0.36, 1] }}
>
{/* Left accent bar — grows from bottom on entry */}
<motion.div
className="absolute left-0 top-0 bottom-0 w-[3px] origin-bottom transition-all duration-[var(--transition-normal)] group-hover:w-1.5"
style={{ backgroundColor: accentColor }}
initial={shouldReduceMotion ? {} : { scaleY: 0 }}
whileInView={{ scaleY: 1 }}
viewport={{ once: true, margin: '-40px' }}
transition={{ duration: 0.4, delay: delay - 0.1 > 0 ? delay - 0.1 : 0, ease: [0.22, 1, 0.36, 1] }}
/>
<div className="p-6 md:p-8 pl-6 md:pl-8">
{/* Large background number */}
<div
className="absolute top-4 right-6 text-[5rem] font-black leading-none select-none pointer-events-none transition-opacity duration-[var(--transition-normal)] group-hover:opacity-10"
style={{ color: 'var(--color-border-light)', opacity: 0.06 }}
aria-hidden="true"
>
{number}
</div>
{/* Number label */}
<div
className="text-xs font-bold tracking-[0.18em] uppercase mb-4"
style={{ color: accentColor }}
>
Service {number}
</div>
<div className="p-6 md:p-8">
{/* Title */}
<h3 className="text-xl md:text-2xl font-bold mb-3 leading-tight text-[var(--color-text-primary)]">
{title}
+1 -3
View File
@@ -44,11 +44,9 @@ export function ServiceGrid({
<div className={`grid grid-cols-1 ${gridCols[columns]} gap-6 md:gap-8`}>
{items.map((item, index) => (
<ServiceCard
key={item.number}
number={item.number}
key={item.title}
title={item.title}
description={item.description}
color={item.color}
href={item.href}
delay={index * 0.1}
/>