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
+10 -22
View File
@@ -2,7 +2,7 @@
import { motion } from 'framer-motion';
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
import { EASE_OUT } from '@/components/ui/page-decoration';
import { CheckCircle2, ChevronDown } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useState } from 'react';
@@ -30,7 +30,6 @@ export function MethodologyFramework({
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
<ScrollReveal className="mb-16 sm:mb-20 max-w-3xl">
<SectionLabel>Methodology</SectionLabel>
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-6">
{title}
</h2>
@@ -53,13 +52,11 @@ export function MethodologyFramework({
transition={{ duration: 0.6, delay: index * 0.08, ease: EASE_OUT }}
>
<div className="flex gap-6 lg:gap-10">
<div className="flex flex-col items-center">
<div className="flex flex-col items-center pt-2">
<div
className="w-12 h-12 rounded-xl flex items-center justify-center text-white font-bold text-lg shrink-0"
className="w-3 h-3 rounded-full shrink-0"
style={{ backgroundColor: accentColor }}
>
{String(index + 1).padStart(2, '0')}
</div>
/>
{index < layers.length - 1 && (
<div className="w-px flex-1 bg-gradient-to-b from-border-primary to-transparent mt-3" />
)}
@@ -109,7 +106,6 @@ export function TechStackShowcase({ title, subtitle, categories }: TechStackShow
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-secondary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
<ScrollReveal className="mb-16 sm:mb-20 max-w-3xl">
<SectionLabel>Tech Stack</SectionLabel>
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-6">
{title}
</h2>
@@ -125,9 +121,8 @@ export function TechStackShowcase({ title, subtitle, categories }: TechStackShow
{categories.map((category, index) => (
<div
key={index}
className="group relative p-8 border border-border-primary bg-bg-primary hover:border-border-secondary hover:bg-bg-secondary transition-all duration-500"
className="group relative p-8 border border-border-primary bg-bg-primary hover:border-border-secondary hover:bg-bg-secondary hover:shadow-lg hover:-translate-y-1 transition-all duration-500"
>
<div className="absolute top-0 left-0 h-1 w-0 group-hover:w-full transition-all duration-700 bg-brand" />
<h3 className="text-lg font-bold text-text-primary mb-5">{category.name}</h3>
<div className="flex flex-wrap gap-2">
{category.items.map((item, i) => (
@@ -173,7 +168,6 @@ export function DeliveryTimeline({
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
<ScrollReveal className="mb-16 sm:mb-20 max-w-3xl">
<SectionLabel>Delivery Process</SectionLabel>
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-6">
{title}
</h2>
@@ -206,11 +200,8 @@ export function DeliveryTimeline({
index % 2 === 0 ? 'lg:ml-12' : 'lg:mr-12'
)}>
<div className="flex items-center justify-between mb-4">
<span
className="text-xs font-bold tracking-[0.2em] uppercase"
style={{ color: accentColor }}
>
{step.phase}
<span className="text-xs font-bold text-text-muted">
{`${index + 1} 阶段:${step.phase}`}
</span>
<span className="text-sm text-text-muted">
{step.duration}
@@ -221,8 +212,8 @@ export function DeliveryTimeline({
{step.description}
</p>
<div className="pt-5 border-t border-border-primary">
<div className="text-xs text-text-muted mb-3 font-medium uppercase tracking-wider">
<div className="text-xs text-text-muted mb-3 font-medium">
</div>
<div className="flex flex-wrap gap-2">
{step.deliverables.map((deliverable, i) => (
@@ -266,7 +257,6 @@ export function FAQSection({ title, subtitle, items }: FAQSectionProps) {
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
<div className="grid lg:grid-cols-12 gap-12 lg:gap-20">
<ScrollReveal className="lg:col-span-4">
<SectionLabel>FAQ</SectionLabel>
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-6">
{title}
</h2>
@@ -341,7 +331,6 @@ export function DataProofSection({ title, subtitle, metrics }: DataProofSectionP
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
<ScrollReveal className="mb-16 sm:mb-20 max-w-3xl">
<SectionLabel>Proven Results</SectionLabel>
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-6">
{title}
</h2>
@@ -357,9 +346,8 @@ export function DataProofSection({ title, subtitle, metrics }: DataProofSectionP
{metrics.map((metric, index) => (
<div
key={index}
className="group relative p-10 lg:p-12 bg-bg-secondary hover:bg-bg-secondary transition-all duration-500"
className="group relative p-10 lg:p-12 bg-bg-secondary hover:bg-bg-secondary hover:shadow-lg transition-all duration-500"
>
<div className="absolute top-0 left-0 h-1 w-0 group-hover:w-full transition-all duration-700 bg-brand" />
<div className="relative z-10">
<div className="text-5xl lg:text-6xl font-bold tracking-tighter mb-4">
<span className="text-text-primary">{metric.value}</span>
@@ -186,17 +186,6 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
transition={{ duration: 0.85, ease: [0.22, 1, 0.36, 1] as const }}
className="max-w-3xl mx-auto text-center mb-20"
>
<motion.span
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15, duration: 0.5 }}
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-brand-soft text-brand text-sm font-bold tracking-wider uppercase mb-5"
>
<Zap className="w-4 h-4" />
</motion.span>
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold tracking-tight text-ink mb-5">
<span className="relative inline-block">
{product.title}
@@ -387,9 +376,6 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
className="mt-24"
>
<div className="text-center mb-14">
<span className="inline-block mb-4 text-sm font-bold text-brand tracking-wider uppercase">
</span>
<h3 className="text-2xl md:text-3xl font-bold tracking-tight text-ink">
</h3>
@@ -65,7 +65,7 @@ export function DetailTrustSection({
caseStudies = [],
dataProofs = [],
certifications = [],
accentColor = '#C41E3A',
accentColor: _accentColor = '#C41E3A',
}: DetailTrustSectionProps) {
const hasContent = caseStudies.length > 0 || dataProofs.length > 0 || certifications.length > 0;
if (!hasContent) return null;
@@ -85,9 +85,6 @@ export function DetailTrustSection({
transition={DESIGN_SYSTEM.effects.scroll.fadeInUp.transition}
className="text-center mb-12"
>
<span className="inline-block mb-3 text-sm font-semibold text-brand tracking-wider uppercase">
</span>
<h2 className={`${DESIGN_SYSTEM.typography.section.title} text-ink`}>
</h2>
@@ -135,9 +132,6 @@ export function DetailTrustSection({
transition={DESIGN_SYSTEM.effects.scroll.fadeInUp.transition}
className="text-center mb-12"
>
<span className="inline-block mb-3 text-sm font-semibold tracking-wider uppercase" style={{ color: accentColor }}>
</span>
<h2 className={`${DESIGN_SYSTEM.typography.section.title} text-ink`}>
</h2>
+2 -6
View File
@@ -25,10 +25,8 @@ export function ServiceValueSection({ service }: ServiceValueSectionProps) {
<section className="relative bg-white py-24 lg:py-32 overflow-hidden">
<div className="container-wide relative">
<SectionHeader
badge="专业服务"
title={`为什么选择我们的${service.title}`}
subtitle={service.overview}
icon={Zap}
/>
<div className="mt-16">
@@ -159,10 +157,8 @@ export function ServiceValueSection({ service }: ServiceValueSectionProps) {
}}
className="relative flex gap-6 group"
>
<div className="relative z-10 shrink-0">
<div className="w-16 h-16 rounded-full bg-white border-4 border-brand flex items-center justify-center shadow-md group-hover:scale-110 transition-transform duration-300">
<span className="text-lg font-bold text-brand">{String(index + 1).padStart(2, '0')}</span>
</div>
<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>
<TiltCard
-2
View File
@@ -20,14 +20,12 @@ export function SolutionValueSection({ solution }: SolutionValueSectionProps) {
<section className="relative bg-white py-24 lg:py-32 overflow-hidden">
<div className="container-wide relative">
<SectionHeader
badge="解决方案"
title={
<>
<span className="text-brand">{solution.industry}</span>
</>
}
subtitle={solution.description}
icon={Lightbulb}
/>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-10 mt-16">
+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}
/>