Files
novalon-website/src/app/(marketing)/home-content-v14.tsx
T

673 lines
29 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 { useRef } from 'react';
import Image from 'next/image';
import { motion } from 'framer-motion';
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
import { Badge } from '@/components/ui/badge';
import { StaticLink } from '@/components/ui/static-link';
import { HeroProductVisual } from '@/components/sections/hero-product-visual';
import { ArrowRight, Lightbulb, Database, Layers, Code, Puzzle, Server } from 'lucide-react';
import { cn } from '@/lib/utils';
import { EASE_OUT } from '@/components/ui/page-decoration';
import { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '@/lib/constants';
import { useSiteConfig } from '@/lib/site-config';
// ===== 服务图标映射:将字符串键名映射为 Lucide 组件 =====
const SERVICE_ICON_MAP: Record<string, React.ReactNode> = {
Lightbulb: <Lightbulb className="w-7 h-7" />,
Code: <Code className="w-7 h-7" />,
Server: <Server className="w-7 h-7" />,
Puzzle: <Puzzle className="w-7 h-7" />,
};
// ===== 服务数据降级兜底(4 条,保证 sm:grid-cols-2 网格完整,避免孤儿格)=====
const FALLBACK_SERVICES = [
{
subtitle: 'Strategy Consulting',
title: '数字化转型战略咨询',
desc: '从业务诊断到数字化路线图,帮助企业识别高价值场景,规划可落地的转型路径,确保每一分投入都能产生可衡量的业务回报。',
highlights: ['业务诊断', '数字化蓝图', '实施路线图', 'ROI 评估'],
metrics: [
{ value: '40%', label: '平均运营效率提升' },
{ value: '6个月', label: '战略到试点周期' },
],
href: '/services/consulting',
icon: <Lightbulb className="w-7 h-7" />,
},
{
subtitle: 'Software Delivery',
title: '企业软件定制开发',
desc: '基于自研产品矩阵与工程化交付流程,提供从需求分析、架构设计到持续运维的全栈软件开发服务,支撑业务数字化落地。',
highlights: ['需求分析', '架构设计', '敏捷开发', '持续运维'],
metrics: [
{ value: '6', label: '款自研产品' },
{ value: '24/7', label: '运维支持' },
],
href: '/services/software',
icon: <Code className="w-7 h-7" />,
},
{
subtitle: 'Data Intelligence',
title: '数据智能与 AI 应用',
desc: '构建企业级数据平台与 AI 能力,打通数据孤岛,沉淀数据资产,让决策从经验驱动转向数据驱动。',
highlights: ['数据中台', 'AI 建模', '可视化分析', '预测决策'],
metrics: [
{ value: '98%', label: '数据质量达标率' },
{ value: '10x', label: '分析效率提升' },
],
href: '/services/data',
icon: <Database className="w-7 h-7" />,
},
{
subtitle: 'Solution Delivery',
title: '行业解决方案落地',
desc: '基于核心团队的行业实践,提供从方案设计、系统开发到上线运维的一站式交付,确保数字化转型真正落地生根。',
highlights: ['行业套件', '定制开发', '系统集成', '持续运维'],
metrics: [
{ value: '6', label: '行业场景覆盖' },
{ value: '99.9%', label: '系统可用性' },
],
href: '/services/solutions',
icon: <Layers className="w-7 h-7" />,
},
];
// ===== 首页结构性文案类型(page-copy CMS 覆盖,未配置时走硬编码兜底)=====
type HomePageCopy = Record<string, any>;
// 多行标题渲染:按 \n 分段,输出 <br/> 换行
function StyledTitle({ lines }: { lines: (string | undefined)[] }) {
return (
<>
{lines.map((line, i) => (
<span key={i}>
{i > 0 && <br />}
{line}
</span>
))}
</>
);
}
// ===== 品牌 CTA 按钮 =====
function CTAButton({ children, href, variant = 'primary', className, dataTestId }: {
children: React.ReactNode;
href: string;
variant?: 'primary' | 'secondary';
className?: string;
dataTestId?: string;
}) {
return (
<a
href={href}
data-testid={dataTestId}
className={cn(
'group inline-flex items-center gap-3 px-10 py-4 font-semibold text-base transition-all duration-200',
variant === 'primary'
? 'bg-brand text-white hover:bg-brand-hover'
: 'text-ink border border-ink/20 hover:border-ink/40 hover:bg-ink/5',
className
)}
>
<span>{children}</span>
<ArrowRight className="w-5 h-5 transition-transform duration-300 group-hover:translate-x-2" />
</a>
);
}
// ===== Hero 区:克制白底 + 品牌红点缀 =====
function HeroSection({ heroData, stats }: {
heroData?: Record<string, any> | null;
stats?: Record<string, any>[];
}) {
const containerRef = useRef<HTMLDivElement>(null);
const siteConfig = useSiteConfig();
const heading = heroData?.heading || heroData?.headingBottom || '让每一家企业都拥有数据驱动的决策能力';
const subheading = heroData?.subheading || heroData?.description || '';
const ctaLabel = heroData?.ctaLabel || '预约咨询';
const ctaHref = heroData?.ctaHref || '/contact';
const secondaryCtaLabel = heroData?.secondaryCtaLabel || '查看产品矩阵';
const secondaryCtaHref = heroData?.secondaryCtaHref || '/products';
const slogan = siteConfig.slogan || COMPANY_INFO.slogan;
const hasValidStats = stats?.length && stats[0]?.value;
const displayStats = hasValidStats ? stats!.slice(0, 3) : [];
return (
<section
ref={containerRef}
className="relative min-h-[90vh] flex items-center overflow-hidden bg-white"
>
{/* 背景纹理:极淡的品牌色点阵 */}
<div className="absolute inset-0 pointer-events-none opacity-[0.03]"
style={{
backgroundImage: `radial-gradient(circle at 25% 25%, rgba(196,30,58,0.4) 1px, transparent 1px)`,
backgroundSize: '48px 48px',
}}
/>
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-16 sm:py-20 md:py-24 lg:py-24">
<div className="grid items-center gap-16 lg:grid-cols-[1.05fr_0.95fr]">
<div className="max-w-4xl">
{/* 品牌标识:Logo 本身已包含印章 + 公司名 + NOVALON */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35, delay: 0, ease: EASE_OUT }}
className="mb-6"
>
<Image
src="/logo.svg"
alt={`${COMPANY_INFO.name} Logo`}
width={224}
height={56}
className="h-12 w-auto sm:h-14"
priority
/>
</motion.div>
{/* 品牌口号 Badge:消费站点配置 slogan,后台可改 */}
{slogan && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35, delay: 0.03, ease: EASE_OUT }}
className="mb-6"
>
<span className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-brand/5 text-brand text-sm font-medium border border-brand/15">
{slogan}
</span>
</motion.div>
)}
{/* 主标题:大胆、直接 */}
<motion.h1
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, delay: 0.06, ease: EASE_OUT }}
className="text-4xl sm:text-5xl md:text-6xl lg:text-6xl 2xl:text-7xl font-black text-ink leading-[1.05] tracking-tight mb-8 sm:mb-10"
>
{heading}
</motion.h1>
{/* 数据指标条:答案优先,数据先行 */}
{displayStats.length > 0 && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35, delay: 0.16, ease: EASE_OUT }}
className="flex flex-wrap gap-8 sm:gap-12 md:gap-16 mb-8"
data-testid="hero-stats"
>
{displayStats.map((stat, i) => (
<div key={i} className="flex items-baseline gap-2">
<span className={cn('text-3xl sm:text-4xl md:text-5xl font-black tabular-nums leading-none', i === 0 ? 'text-brand' : 'text-ink')}>
{stat.value}
</span>
<span className="text-sm text-text-secondary whitespace-nowrap">
{stat.label}
</span>
</div>
))}
</motion.div>
)}
{/* 副标题 */}
{subheading && (
<motion.p
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35, delay: 0.24, ease: EASE_OUT }}
className="text-lg sm:text-xl text-text-secondary max-w-2xl leading-relaxed mb-10"
>
{subheading}
</motion.p>
)}
{/* CTA 按钮组 */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35, delay: 0.3, ease: EASE_OUT }}
className="flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-6"
>
<CTAButton href={ctaHref} dataTestId="hero-primary-cta">{ctaLabel}</CTAButton>
{secondaryCtaLabel && (
<a
href={secondaryCtaHref}
data-testid="hero-secondary-link"
className="group inline-flex items-center gap-2 text-sm font-medium text-text-secondary hover:text-ink transition-colors duration-200 py-4"
>
{secondaryCtaLabel}
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
</a>
)}
</motion.div>
</div>
{/* 右侧:产品视觉(SVG 数据看板示意) */}
<motion.div
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, delay: 0.36, ease: EASE_OUT }}
className="relative mt-16 lg:mt-0"
>
<HeroProductVisual className="max-w-xl w-full lg:ml-auto" />
</motion.div>
</div>
</div>
</section>
);
}
// ===== 信任/成果区:可验证信任信号 + 首批客户共创标签 =====
// 共创计划默认状态条目(如实呈现,无真实案例时不虚构客户)
const DEFAULT_EARLY_ACCESS_STATUS = [
{ label: '共创进行中', desc: '我们正与首批客户共同打磨产品与方案,以真实运营数据验证业务改善。' },
{ label: '产品内测中', desc: '自研产品(如睿视 NovaVis)处于内测阶段,成熟后正式发布。' },
{ label: '成果授权公开', desc: '案例与可量化成果在获得客户授权后公示——经得起衡量。' },
];
function TrustSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
const eyebrow = pageCopy?.trustEyebrow || '值得信赖';
const titleLines = (pageCopy?.trustTitle || '以可衡量的成果\n赢得长期信任').split('\n');
const signals = (pageCopy?.trustSignals?.length ? pageCopy.trustSignals : TRUST_SIGNALS) as typeof TRUST_SIGNALS;
const earlyLabel = pageCopy?.earlyAccessLabel || EARLY_ACCESS.label;
const earlyDesc = pageCopy?.earlyAccessDesc || EARLY_ACCESS.desc;
const earlyTitle = (pageCopy?.earlyAccessTitle as string) || '首批客户共创计划';
const earlyCtaLabel = (pageCopy?.earlyAccessCtaLabel as string) || '成为共创客户';
const earlyStatus = (pageCopy?.earlyAccessStatus?.length ? pageCopy.earlyAccessStatus : DEFAULT_EARLY_ACCESS_STATUS) as typeof DEFAULT_EARLY_ACCESS_STATUS;
return (
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-secondary" data-testid="trust-section">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
<ScrollReveal 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">
{eyebrow}
</span>
</div>
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05]">
<StyledTitle lines={titleLines} />
</h2>
</ScrollReveal>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-10">
{signals.map((item, i) => (
<StaggerReveal key={i} staggerDelay={0.06}>
<div className="bain-card p-6 sm:p-7 h-full flex flex-col">
<div className="flex items-baseline gap-2 mb-3">
<span className="text-xl sm:text-2xl font-black text-ink leading-none">
{item.value}
</span>
<span className="text-sm font-semibold text-text-secondary">{item.label}</span>
</div>
<p className="text-sm text-text-secondary leading-relaxed mb-4 flex-1">{item.desc}</p>
<div className="text-xs text-text-muted border-t border-border-primary pt-3">
数据来源:{item.source}
</div>
</div>
</StaggerReveal>
))}
</div>
{/* 首批客户共创计划:如实呈现共创/内测/授权公开状态,不虚构客户 */}
<StaggerReveal staggerDelay={0.08}>
<div className="p-6 sm:p-8 bg-brand-bg border border-[rgba(196,30,58,0.15)]" data-testid="early-access-banner">
<div className="flex items-center gap-3 mb-4">
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
{earlyLabel}
</span>
</div>
<h3 className="text-xl sm:text-2xl font-bold text-ink mb-3">
{earlyTitle}
</h3>
<p className="text-sm text-text-secondary leading-relaxed mb-6 max-w-2xl">
{earlyDesc}
</p>
<div className="grid sm:grid-cols-3 gap-4 mb-8">
{earlyStatus.map((s) => (
<div key={s.label} className="p-4 bg-white border border-brand/10">
<div className="text-sm font-semibold text-brand mb-1">{s.label}</div>
<div className="text-xs text-text-secondary leading-relaxed">{s.desc}</div>
</div>
))}
</div>
<StaticLink
href="/contact"
className="group inline-flex shrink-0 items-center gap-2 text-sm font-semibold text-ink hover:text-brand transition-colors"
>
<span>{earlyCtaLabel}</span>
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
</StaticLink>
</div>
</StaggerReveal>
</div>
</section>
);
}
// ===== 章节式叙事区:问题 → 方法 → 结果(Storytelling-Driven=====
const NARRATIVE_ACTS = [
{
number: '01',
title: '诊断现状',
desc: '从业务目标出发,梳理流程断点、数据孤岛与瓶颈环节,形成可量化的诊断结论。',
href: '/services/consulting',
cta: '了解诊断方法',
},
{
number: '02',
title: '设计路径',
desc: '基于诊断结论设计系统架构与实施路线图,明确每一阶段的交付物与衡量指标。',
href: '/services/solutions',
cta: '查看方案设计',
},
{
number: '03',
title: '交付成果',
desc: '以自研产品组合与专业实施团队完成交付,用真实运营数据验证业务改善,持续迭代。',
href: '/products',
cta: '浏览产品矩阵',
},
];
function NarrativeSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
const narrativeTitle = (pageCopy?.narrativeTitle || '从问题到结果,\n一条可量化的路径').split('\n');
const NARRATIVE_ACTS_CMS = (pageCopy?.narrativeActs?.length ? pageCopy.narrativeActs : NARRATIVE_ACTS) as Array<{
number: string;
title: string;
desc: string;
href: string;
cta: string;
}>;
return (
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-white" data-testid="narrative-section">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
<ScrollReveal className="mb-16 sm:mb-20">
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05]">
<StyledTitle lines={narrativeTitle} />
</h2>
</ScrollReveal>
<div className="grid md:grid-cols-3 gap-px bg-border-primary">
{NARRATIVE_ACTS_CMS.map((act) => (
<StaggerReveal key={act.number} staggerDelay={0.08}>
<div className="group relative h-full bg-white p-8 sm:p-10 md:p-12 transition-colors duration-300 hover:bg-bg-secondary">
<div className="flex items-start justify-between mb-8">
<span
aria-hidden="true"
className="text-4xl sm:text-5xl font-black text-text-hint tabular-nums leading-none"
>
{act.number}
</span>
</div>
<h3 className="text-xl lg:text-2xl font-bold text-ink mb-4 group-hover:text-brand transition-colors duration-300">
{act.title}
</h3>
<p className="text-text-secondary leading-relaxed mb-8 text-sm">
{act.desc}
</p>
<div className="pt-6 border-t border-border-primary">
<a
href={act.href}
className="group/link inline-flex items-center gap-2 text-sm font-semibold text-text-secondary group-hover:text-brand transition-colors"
>
<span>{act.cta}</span>
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover/link:translate-x-1" />
</a>
</div>
</div>
</StaggerReveal>
))}
</div>
</div>
</section>
);
}
// ===== 服务区:等宽三列卡片 + 兜底数据 =====
function ServicesSection({ services, pageCopy }: { services?: Record<string, any>[]; pageCopy?: HomePageCopy | null }) {
const hasValidData = services?.length && (services![0] as any)?.subtitle;
const SERVICE_ITEMS = hasValidData
? services!.map((s: Record<string, any>) => ({
...s,
icon: SERVICE_ICON_MAP[s.icon as string] || <Server className="w-7 h-7" />,
})) as Record<string, any>[]
: FALLBACK_SERVICES;
const servicesTitle = (pageCopy?.servicesTitle || '从战略到落地,\n可量化的一站式服务').split('\n');
return (
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-secondary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
<div className="max-w-3xl mb-16 sm:mb-20">
<ScrollReveal>
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05]">
<StyledTitle lines={servicesTitle} />
</h2>
</ScrollReveal>
</div>
<div className="grid gap-6 sm:grid-cols-2">
{SERVICE_ITEMS.map((service, i) => (
<StaggerReveal key={i} staggerDelay={0.05}>
<a href={service.href} className="group bain-card block p-8 sm:p-10 md:p-12 h-full flex flex-col">
<div className="flex flex-col flex-1">
<div className="flex items-start justify-between mb-10">
<div className="w-12 h-12 flex items-center justify-center bg-bg-secondary text-text-secondary" aria-hidden="true">
{service.icon}
</div>
</div>
<div className="mb-6">
<span className="block text-sm text-text-muted mb-3 font-medium">{service.subtitle}</span>
<h3 className="text-xl lg:text-2xl font-bold text-ink">{service.title}</h3>
</div>
<p className="text-text-secondary leading-relaxed mb-8 text-sm">{service.desc}</p>
<div className="flex flex-wrap gap-2 mb-8">
{service.highlights.map((h: string, j: number) => (
<span key={j} className="px-3 py-1.5 text-xs text-text-muted border border-border-primary font-medium">
{h}
</span>
))}
</div>
{(service as any).metrics && (
<div className="flex gap-6 mb-8 pb-8 border-b border-border-primary">
{(service as any).metrics.map((m: any, j: number) => (
<div key={j}>
<div className="text-2xl sm:text-3xl font-black text-ink leading-none mb-1">{m.value}</div>
<div className="text-xs text-text-muted font-medium">{m.label}</div>
</div>
))}
</div>
)}
<div className="mt-auto pt-2">
<div className="inline-flex items-center gap-2 text-sm font-semibold text-text-secondary group-hover:text-brand transition-colors">
<span>了解详情</span>
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
</div>
</div>
</div>
</a>
</StaggerReveal>
))}
</div>
</div>
</section>
);
}
// ===== 案例区:轻量快照卡片 =====
function CasesSection({ cases, pageCopy }: { cases?: Record<string, any>[]; pageCopy?: HomePageCopy | null }) {
const hasValidData = cases?.length && cases[0]?.metrics;
const CASE_ITEMS = hasValidData ? cases! : [];
const casesEyebrow = pageCopy?.casesEyebrow || '共创进行时';
const casesTitleLines = (pageCopy?.casesTitle || '每一个项目,\n都以业务改善为衡量标准').split('\n');
const casesDesc = pageCopy?.casesDescription || '我们正与首批客户共同打磨产品与方案,以真实运营数据验证业务改善。';
const earlyLabel = pageCopy?.earlyAccessLabel || EARLY_ACCESS.label;
const earlyDesc = pageCopy?.earlyAccessDesc || EARLY_ACCESS.desc;
if (CASE_ITEMS.length === 0) {
return (
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-white">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
<div className="grid lg:grid-cols-2 gap-12 items-center">
<ScrollReveal>
<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">
{casesEyebrow}
</span>
</div>
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05] mb-8">
<StyledTitle lines={casesTitleLines} />
</h2>
<p className="text-text-secondary leading-relaxed text-lg mb-8">
{casesDesc}
</p>
<StaticLink
href="/contact"
className="group inline-flex items-center gap-3 text-ink font-semibold text-sm"
>
成为首批共创客户
<ArrowRight className="w-4 h-5 transition-transform duration-300 group-hover:translate-x-2 text-text-secondary group-hover:text-brand" />
</StaticLink>
</ScrollReveal>
<ScrollReveal delay={0.15}>
<div className="bain-card p-10 sm:p-12 bg-bg-secondary">
<div className="text-5xl sm:text-6xl font-black text-brand tabular-nums leading-none mb-4">
{earlyLabel}
</div>
<p className="text-text-secondary leading-relaxed">{earlyDesc}</p>
</div>
</ScrollReveal>
</div>
</div>
</section>
);
}
return (
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-white">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
<div className="flex flex-col md:flex-row md:items-end md:justify-between gap-8 mb-16 sm:mb-20">
<ScrollReveal className="md:max-w-3xl">
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05]">
<StyledTitle lines={casesTitleLines} />
</h2>
</ScrollReveal>
<ScrollReveal delay={0.1}>
<StaticLink
href="/cases"
className="group inline-flex items-center gap-3 text-ink font-semibold text-sm"
>
查看全部案例
<ArrowRight className="w-4 h-5 transition-transform duration-300 group-hover:translate-x-2 text-text-secondary group-hover:text-brand" />
</StaticLink>
</ScrollReveal>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{CASE_ITEMS.map((caseItem, i) => (
<ScrollReveal key={i} delay={i * 0.1}>
<a
href={caseItem.href || `/cases/${caseItem.slug || ''}`}
className="group bain-card block p-8 sm:p-10 h-full flex flex-col"
>
<div className="flex items-center gap-3 mb-8">
<Badge className="bg-bg-secondary text-ink border-transparent">
{caseItem.industry}
</Badge>
</div>
<h3 className="text-xl lg:text-2xl font-bold text-ink mb-8 leading-[1.2]">
{caseItem.title}
</h3>
<div className="grid grid-cols-3 gap-4 mb-8">
{caseItem.metrics.slice(0, 3).map((metric: Record<string, any>, j: number) => (
<div key={j}>
<div className={cn(
'text-xl sm:text-2xl font-black leading-none mb-2',
j === 0 ? 'text-brand' : 'text-ink'
)}>
{metric.value}
</div>
<div className="text-xs text-text-secondary font-medium leading-tight">
{metric.label}
</div>
</div>
))}
</div>
<div className="mt-auto pt-4 border-t border-border-primary">
<div className="inline-flex items-center gap-2 text-sm font-semibold text-text-secondary group-hover:text-brand transition-colors">
<span>了解详情</span>
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
</div>
</div>
</a>
</ScrollReveal>
))}
</div>
</div>
</section>
);
}
// ===== CTA 区:克制强调,品牌红仅用于按钮 =====
function CTASection({ heroData, pageCopy }: { heroData?: Record<string, any> | null; pageCopy?: HomePageCopy | null }) {
const ctaTitleLine1 = heroData?.ctaTitleLine1 || pageCopy?.ctaTitleLine1 || '准备好开始了吗?';
const ctaTitleLine2 = heroData?.ctaTitleLine2 || pageCopy?.ctaTitleLine2 || '让数据真正转化为业务增长';
const ctaLabel = heroData?.ctaLabel || pageCopy?.ctaLabel || '预约咨询';
const ctaHref = heroData?.ctaHref || '/contact';
return (
<section className="relative py-24 sm:py-32 md:py-40 overflow-hidden bg-bg-secondary">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
<ScrollReveal className="max-w-3xl">
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05] mb-10 sm:mb-14">
{ctaTitleLine1}
<br />
{ctaTitleLine2}
</h2>
<CTAButton href={ctaHref}>{ctaLabel}</CTAButton>
</ScrollReveal>
</div>
</section>
);
}
// ===== 主页面组件 =====
export default function HomeContentV14({ services, cases, stats, heroData, pageCopy }: {
services?: Record<string, any>[];
cases?: Record<string, any>[];
stats?: Record<string, any>[];
heroData?: Record<string, any> | null;
pageCopy?: HomePageCopy | null;
}) {
return (
<main>
{/* 1. Hero:克制白底 + 品牌红点缀 */}
<HeroSection heroData={heroData} stats={stats} />
{/* 2. 信任区:量化数据成果展示 */}
<TrustSection pageCopy={pageCopy} />
{/* 3. 章节式叙事:问题 → 方法 → 结果 */}
<NarrativeSection pageCopy={pageCopy} />
{/* 4. 服务区:等宽卡片 + 兜底数据 */}
<ServicesSection services={services} pageCopy={pageCopy} />
{/* 5. 案例区:轻量快照卡片 */}
<CasesSection cases={cases} pageCopy={pageCopy} />
{/* 6. CTA:克制强调,品牌红仅用于按钮 */}
<CTASection heroData={heroData} pageCopy={pageCopy} />
</main>
);
}