安全/鉴权:登出清服务端 httpOnly 令牌、登录 IP 限流前置 bcrypt、停用账号 令牌在 6 个内联 role handler 失效、admin/items 存在性 oracle 认证前置、 同意偏好 sanitize fail-closed、api-crypto 按 HTTP 方法判体。 CMS:workflow 状态更新+审计收进交互式 $transaction(失败即回滚)、编辑器 richtext 走 textarea 防换行损毁、array/object 子字段按作用域读写防污染、 about/contact/erp-upgrade 补 ISR revalidate 与路由映射。 UI/a11y:AnimatedCounter 入视口前显起点、CLS 累计上报、MetricCard 动画入 effect 并卸载取消 rAF、后台状态四态映射/搜索防抖+序号守卫/媒体单 input/ 吞错改横幅/表单 label-aria 关联。 新增对应判别性单测与 pagination/rate-limit/client-ip/sanitize/validate-content-data 等模块及 tests-integration 真库集成层。
995 lines
48 KiB
TypeScript
995 lines
48 KiB
TypeScript
'use client';
|
||
|
||
import { useRef } from 'react';
|
||
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 { HeroParticleField, type HeroVariant } from '@/components/sections/hero-particle-field';
|
||
import { ArrowRight, Lightbulb, Database, Layers, Code, Puzzle, Server, Quote, Calendar } from 'lucide-react';
|
||
import { cn } from '@/lib/utils';
|
||
import { EASE_OUT } from '@/components/ui/page-decoration';
|
||
import { CTAButton } from '@/components/ui/cta-button';
|
||
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
||
import { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '@/lib/constants';
|
||
import { canonicalCtaLabel, CTA_LABELS } from '@/lib/constants/cta-labels';
|
||
import { canonicalTrustSource } from '@/lib/constants/company';
|
||
import { weakestBasis } from '@/lib/constants/metrics-basis';
|
||
import { useSiteConfig } from '@/lib/site-config';
|
||
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
||
import { type NewsItem } from '@/lib/constants/news';
|
||
import { NEWS } from '@/lib/constants/news';
|
||
|
||
// ===== 服务图标映射:将字符串键名映射为 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" />,
|
||
},
|
||
];
|
||
|
||
// ===== 行业洞察兜底数据(对标 Accenture Insights:类型标签 + 卡片)=====
|
||
// 零编造原则:全部为可验证的方法论/观点类内容,链接真实页面;不做虚构研究报告
|
||
const FALLBACK_INSIGHTS = [
|
||
{
|
||
tag: '方法论',
|
||
title: '数字化转型诊断:从哪里开始,如何衡量',
|
||
desc: '从业务目标出发的四步诊断法——梳理断点、量化差距、排定优先级、定义验收指标。',
|
||
date: '2026-08',
|
||
href: '/methodology',
|
||
},
|
||
{
|
||
tag: '观点',
|
||
title: '私有化部署:为什么核心数据需要留在企业内',
|
||
desc: '合规要求之外,数据主权正在成为企业数字化决策的第一优先级。',
|
||
date: '2026-08',
|
||
href: '/services/data',
|
||
},
|
||
{
|
||
tag: '方法论',
|
||
title: '从需求到交付:全流程陪伴式实施如何降低转型风险',
|
||
desc: '诊断 → 设计 → 交付 → 运维,每一阶段都有明确的交付物与衡量指标。',
|
||
date: '2026-07',
|
||
href: '/services/software',
|
||
},
|
||
];
|
||
|
||
// ===== 创始人观点兜底(对标 Accenture CEO Quote,人格化背书)=====
|
||
// 零编造原则:不虚构具体人名/原话;姓名职务为空时仅展示观点立场,真实引言可在 CMS page-copy 配置
|
||
const FALLBACK_FOUNDER_QUOTE = {
|
||
// 排版约束:3 行均势(17 / 18 / 13 字),避开"量。"孤儿字。
|
||
// 配合 blockquote 的 whitespace-pre-line 渲染换行。
|
||
text: '我们相信,数字化不是一次性项目,\n而是持续迭代的能力建设。\n每一份投入,都应当经得起衡量。',
|
||
name: '睿新致远创始团队',
|
||
role: '技术咨询 · 数字化转型同行者',
|
||
};
|
||
|
||
// ===== 首页结构性文案类型(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 按钮已抽离为可复用 UI 组件:@/components/ui/cta-button =====
|
||
|
||
// ===== Hero 区:浅色画布 + 精密工程网格科技感 + 品牌红单电压 =====
|
||
// 设计方向(2026-08-31 重设计,参考 Accenture Radical Relevance;2026-09-01 科技感升级):
|
||
// - 声明先行:H1 → 主张 → CTA,一屏只负责「一句话说清你是谁 + 一个行动」
|
||
// - 数据指标下沉为独立成果带(HeroStatsBand),由滚动触发(Glance 层),不再挤在 hero 内
|
||
// - 单一电压:hero 内只有品牌红一个彩色;连线走墨色(浅底)/中性灰(深底),不抢品牌红
|
||
// - 字重收敛:大标题 700(原 900),对齐 Accenture「字重 9→3」的克制
|
||
// - 科技感引擎:HeroParticleField 变体(固定种子、reduced-motion/自动化降级 → 快照零漂移)
|
||
//
|
||
// ===== HERO_VARIANT 观感开关(默认 'grid':浅色底 + 工程网格科技感)=====
|
||
// 定调:Hero 保持浅色背景(与全站浅色咨询风一致),科技感由「精密工程网格 + 动态粒子 + 墨色细连线」承担,
|
||
// 不做深色 Hero。深色变体保留待选(改这一行即生效,无需改其它代码):
|
||
// 'grid' 浅色工程网格 + 品牌红漂移节点 + 墨色细连线 ← 默认落地
|
||
// 'network' 深色节点网络(品牌红节点 + 中性灰连线)
|
||
// 'constellation' 深色数据星图(品牌红脉冲节点 + 暗白次级点 + 竖向数据流)
|
||
// 'spotlight' 深色聚光 + 粒子 + 工程网格(纵深最强,复用 L2 光标聚光)
|
||
const HERO_VARIANT: HeroVariant = 'grid';
|
||
|
||
function HeroSection({ heroData }: {
|
||
heroData?: Record<string, any> | null;
|
||
}) {
|
||
const containerRef = useRef<HTMLDivElement>(null);
|
||
const spotRef = useRef<HTMLDivElement>(null);
|
||
const siteConfig = useSiteConfig();
|
||
const reduceMotion = useReducedMotion();
|
||
|
||
// 科技感变体开关(见文件顶部 HERO_VARIANT 注释):深色变体统一走深色画布 + 主题感知文字
|
||
const dark = HERO_VARIANT !== 'grid';
|
||
const showGrid = HERO_VARIANT === 'constellation' || HERO_VARIANT === 'spotlight'; // 深色工程网格
|
||
const showLightGrid = HERO_VARIANT === 'grid'; // 浅色工程网格(墨色 hairline,精密质感)
|
||
const showCursorGlow = HERO_VARIANT === 'spotlight';
|
||
|
||
const heading = heroData?.heading || heroData?.headingBottom || '让每一家企业都拥有数据驱动的决策能力';
|
||
const subheading = heroData?.subheading || heroData?.description || '';
|
||
const ctaLabel = canonicalCtaLabel(heroData?.ctaLabel) || CTA_LABELS.consult;
|
||
const ctaHref = heroData?.ctaHref || '/contact';
|
||
const secondaryCtaLabel = heroData?.secondaryCtaLabel || '查看产品矩阵';
|
||
const secondaryCtaHref = heroData?.secondaryCtaHref || '/products';
|
||
const slogan = siteConfig.slogan || COMPANY_INFO.slogan;
|
||
|
||
// reduced-motion 守卫:与 ScrollReveal 同范式——reduce 时传空 initial/animate,
|
||
// 元素直接以自然(可见)状态渲染,零位移/淡入;默认(非 reduce,含首帧 null)正常动画。
|
||
const rise = (y: number, delay: number, duration = 0.3) => ({
|
||
initial: reduceMotion ? {} : { opacity: 0, y },
|
||
animate: reduceMotion ? {} : { opacity: 1, y: 0 },
|
||
transition: { duration: reduceMotion ? 0 : duration, delay: reduceMotion ? 0 : delay, ease: EASE_OUT },
|
||
});
|
||
|
||
return (
|
||
<section
|
||
ref={containerRef}
|
||
className={`relative min-h-[90vh] flex items-center overflow-hidden ${dark ? 'bg-dark-bg text-white' : 'bg-bg-primary'}`}
|
||
data-testid="hero-section"
|
||
onPointerMove={(e) => {
|
||
if (!spotRef.current || !containerRef.current) return;
|
||
const rect = containerRef.current.getBoundingClientRect();
|
||
spotRef.current.style.setProperty('--spot-x', `${e.clientX - rect.left}px`);
|
||
spotRef.current.style.setProperty('--spot-y', `${e.clientY - rect.top}px`);
|
||
}}
|
||
>
|
||
{/* L1 工程网格:浅底墨色 hairline(grid)/ 深底白色 hairline(constellation、spotlight) */}
|
||
{showLightGrid && (
|
||
<div className="absolute inset-0 pointer-events-none hero-grid-overlay--light" aria-hidden="true" />
|
||
)}
|
||
{showGrid && (
|
||
<div className="absolute inset-0 pointer-events-none hero-grid-overlay--dark" aria-hidden="true" />
|
||
)}
|
||
{/* L2 品牌红光标聚光(仅 spotlight 变体 + 非 reduced-motion):跟随鼠标,仅写 2 个 CSS 变量,无 canvas、无逐帧 JS */}
|
||
{showCursorGlow && !reduceMotion && (
|
||
<div ref={spotRef} className="absolute inset-0 pointer-events-none hero-spot-glow opacity-70" aria-hidden="true" />
|
||
)}
|
||
{/* L3 动态粒子(品牌红点缀 + 科技感变体):rAF 持续循环漂移;
|
||
reduced-motion / 自动化环境下降级为一次性静态帧(连线/星图位置由固定种子复现 → 快照零漂移) */}
|
||
<HeroParticleField variant={HERO_VARIANT} dark={dark} />
|
||
|
||
<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">
|
||
<div className="max-w-4xl">
|
||
{/* 品牌口号 Badge:画布上的品牌红信号(深底走 brand-light 保证对比度;hero 不重复渲染 Logo,避免与导航栏重复) */}
|
||
{slogan && (
|
||
<motion.div
|
||
{...rise(20, 0.03)}
|
||
className="mb-6"
|
||
>
|
||
<span className={`inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium border ${dark ? 'bg-brand/10 text-brand-light border-brand/30' : 'bg-brand/5 text-brand-ink border-brand/15'}`}>
|
||
{slogan}
|
||
</span>
|
||
</motion.div>
|
||
)}
|
||
|
||
{/* 主标题:大胆、直接(深底白色 / 浅底墨色,随 HERO_VARIANT 切换) */}
|
||
<motion.h1
|
||
{...rise(40, 0.06, 0.45)}
|
||
className={`text-4xl sm:text-5xl md:text-6xl lg:text-6xl 2xl:text-7xl font-bold leading-[1.05] tracking-tight mb-7 sm:mb-8 ${dark ? 'text-white' : 'text-ink'}`}
|
||
>
|
||
{heading}
|
||
</motion.h1>
|
||
|
||
{/* 主张:紧随声明之后,回答「凭什么信你」(声明先行,数据下沉至成果带) */}
|
||
{subheading && (
|
||
<motion.p
|
||
{...rise(30, 0.16)}
|
||
className={`text-lg sm:text-xl max-w-2xl leading-relaxed mb-10 ${dark ? 'text-white/70' : 'text-text-secondary'}`}
|
||
>
|
||
{subheading}
|
||
</motion.p>
|
||
)}
|
||
|
||
{/* CTA 按钮组 */}
|
||
<motion.div
|
||
{...rise(30, 0.24)}
|
||
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 transition-colors duration-200 py-4 ${dark ? 'text-white/70 hover:text-white' : 'text-text-secondary hover:text-ink'}`}
|
||
>
|
||
{secondaryCtaLabel}
|
||
<ArrowRight className="w-4 h-4 transition-transform duration-normal group-hover:translate-x-1" />
|
||
</a>
|
||
)}
|
||
</motion.div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ===== Hero 成果带:数据指标下沉为独立带(声明先行,Glance 层滚动触发)=====
|
||
// 设计意图:hero 一屏只负责「一句话声明 + 一个行动」,硬指标移出后由滚动触发,
|
||
// 既是 Accenture「single stream 逐步展开」的落地,也避免一屏信息过载。
|
||
function HeroStatsBand({ stats }: { stats?: Record<string, any>[] }) {
|
||
const hasValidStats = stats?.length && stats[0]?.value;
|
||
const displayStats = hasValidStats ? stats!.slice(0, 3) : [];
|
||
if (displayStats.length === 0) return null;
|
||
return (
|
||
<section
|
||
data-testid="hero-stats"
|
||
aria-label="关键成果指标"
|
||
className="relative border-t border-border-primary bg-bg-primary"
|
||
>
|
||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 py-12 sm:py-16">
|
||
<ScrollReveal>
|
||
<div className="flex flex-wrap gap-8 sm:gap-12 md:gap-16">
|
||
{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-mono font-bold tabular-nums leading-none',
|
||
i === 0 ? 'text-brand-ink' : 'text-ink'
|
||
)}>
|
||
{stat.value}
|
||
</span>
|
||
<span className="text-sm text-text-secondary whitespace-nowrap">
|
||
{stat.label}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</ScrollReveal>
|
||
<MetricsBasisNote basis={weakestBasis(displayStats.map((s) => s.basis))} className="mt-6" />
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ===== 信任/成果区:可验证信任信号 =====
|
||
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;
|
||
|
||
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-ink">
|
||
{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">
|
||
数据来源:{canonicalTrustSource(item.source)}
|
||
</div>
|
||
</div>
|
||
</StaggerReveal>
|
||
))}
|
||
</div>
|
||
</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-bg-primary" 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.05}>
|
||
<div className="group relative h-full bg-bg-primary p-8 sm:p-10 md:p-12 transition-colors duration-normal 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-ink transition-colors duration-normal">
|
||
{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 min-h-6 gap-2 text-sm font-semibold text-text-secondary group-hover:text-brand-ink transition-colors"
|
||
>
|
||
<span>{act.cta}</span>
|
||
<ArrowRight className="w-4 h-4 transition-transform duration-normal group-hover/link:translate-x-1" />
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</StaggerReveal>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ===== 行业洞察区(对标 Accenture Insights:类型标签 + 内容卡片,思想领导力支柱)=====
|
||
function InsightsSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
|
||
const eyebrow = (pageCopy?.insightsEyebrow as string) || 'Insights';
|
||
const titleLines = ((pageCopy?.insightsTitle as string) || '我们的思考,\n以实践为锚').split('\n');
|
||
const desc = (pageCopy?.insightsDescription as string) || '方法论、观点与共创进展——可验证的思考,不空谈趋势。';
|
||
const items = (pageCopy?.insightsItems?.length ? pageCopy.insightsItems : FALLBACK_INSIGHTS) as Array<{
|
||
tag: string;
|
||
title: string;
|
||
desc: string;
|
||
date?: string;
|
||
href: string;
|
||
}>;
|
||
|
||
return (
|
||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-primary" data-testid="insights-section">
|
||
<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">
|
||
<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-ink">
|
||
{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>
|
||
<p className="mt-6 text-base sm:text-lg text-text-secondary leading-relaxed max-w-2xl">
|
||
{desc}
|
||
</p>
|
||
</ScrollReveal>
|
||
<ScrollReveal delay={0.1}>
|
||
<StaticLink
|
||
href="/news"
|
||
className="group inline-flex items-center min-h-6 gap-3 text-ink font-semibold text-sm"
|
||
>
|
||
查看全部洞察
|
||
<ArrowRight className="w-4 h-5 transition-transform duration-normal group-hover:translate-x-2 text-text-secondary group-hover:text-brand-ink" />
|
||
</StaticLink>
|
||
</ScrollReveal>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-px bg-border-primary border border-border-primary">
|
||
{items.slice(0, 4).map((insight, i) => (
|
||
<StaggerReveal key={i} staggerDelay={0.05}>
|
||
<a
|
||
href={insight.href}
|
||
className="group flex flex-col h-full bg-bg-primary p-8 sm:p-10 md:p-12 transition-colors duration-normal hover:bg-bg-secondary"
|
||
>
|
||
<div className="flex items-center justify-between mb-8">
|
||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand-ink">
|
||
{insight.tag}
|
||
</span>
|
||
{insight.date && (
|
||
<span className="text-xs text-text-muted tabular-nums">{insight.date}</span>
|
||
)}
|
||
</div>
|
||
<h3 className="text-xl lg:text-2xl font-bold text-ink mb-4 leading-[1.25] group-hover:text-brand-ink transition-colors duration-normal">
|
||
{insight.title}
|
||
</h3>
|
||
<p className="text-text-secondary leading-relaxed mb-8 text-sm flex-1">
|
||
{insight.desc}
|
||
</p>
|
||
<div className="pt-6 border-t border-border-primary">
|
||
<span className="inline-flex items-center gap-2 text-sm font-semibold text-text-secondary group-hover:text-brand-ink transition-colors">
|
||
了解详情
|
||
<ArrowRight className="w-4 h-4 transition-transform duration-normal group-hover:translate-x-1" />
|
||
</span>
|
||
</div>
|
||
</a>
|
||
</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">
|
||
{(Array.isArray(service.highlights) ? 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="mb-8 pb-8 border-b border-border-primary">
|
||
<div className="flex gap-6 mb-2">
|
||
{(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>
|
||
<MetricsBasisNote basis={weakestBasis(((service as any).metrics as any[]).map((m) => m.basis))} />
|
||
</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-ink transition-colors">
|
||
<span>了解详情</span>
|
||
<ArrowRight className="w-4 h-4 transition-transform duration-normal group-hover:translate-x-1" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</a>
|
||
</StaggerReveal>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ===== 创始人观点区(对标 Accenture CEO Quote:大引言 + 署名,人格化背书)=====
|
||
function FounderQuoteSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
|
||
const eyebrow = (pageCopy?.founderQuoteEyebrow as string) || '创始团队观点';
|
||
const quoteText = (pageCopy?.founderQuoteText as string) || FALLBACK_FOUNDER_QUOTE.text;
|
||
const quoteName = (pageCopy?.founderQuoteName as string) || FALLBACK_FOUNDER_QUOTE.name;
|
||
const quoteRole = (pageCopy?.founderQuoteRole as string) || FALLBACK_FOUNDER_QUOTE.role;
|
||
|
||
return (
|
||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-brand-section" data-testid="founder-quote-section">
|
||
{/* 背景纹理:极淡白色点阵(Bain 式深红区块 + 克制质感) */}
|
||
<div className="absolute inset-0 pointer-events-none opacity-[0.05]"
|
||
style={{
|
||
backgroundImage: `radial-gradient(circle at 75% 25%, rgba(255,255,255,0.5) 1px, transparent 1px)`,
|
||
backgroundSize: '48px 48px',
|
||
}}
|
||
/>
|
||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||
<ScrollReveal className="max-w-4xl mx-auto text-center">
|
||
<div className="bg-bg-primary/95 rounded-2xl p-8 sm:p-10 md:p-12 max-w-3xl mx-auto shadow-sm border border-brand/10">
|
||
<div className="flex items-center justify-center gap-3 mb-8">
|
||
<div className="w-10 h-px bg-brand/40" />
|
||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand-ink">
|
||
{eyebrow}
|
||
</span>
|
||
<div className="w-10 h-px bg-brand/40" />
|
||
</div>
|
||
<Quote className="w-10 h-10 mx-auto mb-8 text-brand-ink/40" aria-hidden="true" />
|
||
<blockquote className="text-base sm:text-xl md:text-[28px] font-medium text-ink leading-[1.7] mb-10 border-l-0 pl-0 m-0 max-w-[640px] mx-auto whitespace-pre-line">
|
||
{quoteText}
|
||
</blockquote>
|
||
{(quoteName || quoteRole) && (
|
||
<div className="flex flex-col items-center gap-1">
|
||
{quoteName && (
|
||
<div className="text-base font-semibold text-ink">{quoteName}</div>
|
||
)}
|
||
{quoteRole && (
|
||
<div className="text-sm text-text-secondary">{quoteRole}</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</ScrollReveal>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ===== 共创流程(可验证方法论,不虚构客户)=====
|
||
const CO_CREATION_STEPS = [
|
||
{ step: '01', title: '业务诊断', desc: '深入理解业务场景,识别高价值切入点' },
|
||
{ step: '02', title: '方案共创', desc: '与核心团队共同设计解决方案与实施路径' },
|
||
{ step: '03', title: '验证发布', desc: '以真实运营数据验证成效,经授权后公开案例' },
|
||
];
|
||
|
||
// ===== 案例区:Client Spotlight(对标 Accenture:深色大卡 spotlight + 其余小卡)=====
|
||
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-bg-primary">
|
||
<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-ink">
|
||
{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">
|
||
{casesDesc}
|
||
</p>
|
||
</ScrollReveal>
|
||
<ScrollReveal delay={0.15}>
|
||
<div className="relative overflow-hidden rounded-[2rem] bg-dark-bg p-10 sm:p-12 h-full flex flex-col shadow-card">
|
||
{/* 背景纹理:极淡白色点阵(对标埃森哲深色 spotlight 画布) */}
|
||
<div
|
||
className="absolute inset-0 pointer-events-none opacity-[0.05]"
|
||
style={{
|
||
backgroundImage: `radial-gradient(circle at 75% 25%, rgba(255,255,255,0.5) 1px, transparent 1px)`,
|
||
backgroundSize: '48px 48px',
|
||
}}
|
||
/>
|
||
<div className="relative z-10 flex flex-col h-full">
|
||
<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-light">
|
||
{casesEyebrow}
|
||
</span>
|
||
</div>
|
||
<Quote className="w-8 h-8 mb-6 text-white/30" aria-hidden="true" />
|
||
<div className="text-4xl sm:text-5xl font-black text-white tabular-nums leading-none mb-4">
|
||
{earlyLabel}
|
||
</div>
|
||
<p className="text-white/70 leading-relaxed mb-10">{earlyDesc}</p>
|
||
<ol className="space-y-5 mb-10">
|
||
{CO_CREATION_STEPS.map((s) => (
|
||
<li key={s.step} className="flex items-start gap-4">
|
||
<span className="text-sm font-black text-brand-light tabular-nums leading-6 shrink-0">
|
||
{s.step}
|
||
</span>
|
||
<div>
|
||
<div className="text-sm font-semibold text-white mb-0.5">{s.title}</div>
|
||
<div className="text-sm text-white/60 leading-relaxed">{s.desc}</div>
|
||
</div>
|
||
</li>
|
||
))}
|
||
</ol>
|
||
<div className="mt-auto">
|
||
<CTAButton href="/contact" dataTestId="cases-co-creation-cta">
|
||
{CTA_LABELS.consult}
|
||
</CTAButton>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ScrollReveal>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
const [spotlightCase, ...restCases] = CASE_ITEMS;
|
||
const spotlightTestimonial = spotlightCase?.testimonial as Record<string, any> | undefined;
|
||
|
||
return (
|
||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-primary">
|
||
<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 min-h-6 gap-3 text-ink font-semibold text-sm"
|
||
>
|
||
查看全部案例
|
||
<ArrowRight className="w-4 h-5 transition-transform duration-normal group-hover:translate-x-2 text-text-secondary group-hover:text-brand-ink" />
|
||
</StaticLink>
|
||
</ScrollReveal>
|
||
</div>
|
||
|
||
{/* Client Spotlight 大卡:深色画布 + 客户引言 + 量化成果(对标 Accenture) */}
|
||
{spotlightCase && (
|
||
<ScrollReveal className="mb-6">
|
||
<a
|
||
href={spotlightCase.href || `/cases/${spotlightCase.slug || ''}`}
|
||
className="group relative block overflow-hidden rounded-[2rem] bg-dark-bg p-10 sm:p-14 h-full flex flex-col shadow-card"
|
||
>
|
||
{/* 背景纹理:极淡白色点阵 */}
|
||
<div
|
||
className="absolute inset-0 pointer-events-none opacity-[0.05]"
|
||
style={{
|
||
backgroundImage: `radial-gradient(circle at 75% 25%, rgba(255,255,255,0.5) 1px, transparent 1px)`,
|
||
backgroundSize: '48px 48px',
|
||
}}
|
||
/>
|
||
<div className="relative z-10 flex flex-col lg:flex-row lg:items-stretch gap-10 lg:gap-16">
|
||
<div className="flex flex-col flex-1">
|
||
<div className="flex items-center gap-3 mb-8">
|
||
<Badge className="bg-white/10 text-white border-transparent">
|
||
{spotlightCase.industry}
|
||
</Badge>
|
||
{spotlightCase.verified && (
|
||
<Badge className="bg-brand/15 text-brand-light border-brand/30">
|
||
已获客户授权
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
<h3 className="text-2xl sm:text-3xl lg:text-4xl font-black text-white leading-[1.15] mb-8">
|
||
{spotlightCase.title}
|
||
</h3>
|
||
{spotlightTestimonial?.quote && (
|
||
<blockquote className="max-w-2xl">
|
||
<Quote className="w-6 h-6 text-brand-light mb-4" aria-hidden="true" />
|
||
<p className="text-white/80 text-lg leading-relaxed">
|
||
{spotlightTestimonial.quote}
|
||
</p>
|
||
{(spotlightTestimonial.author || spotlightTestimonial.name) && (
|
||
<footer className="mt-4 text-sm text-white/60">
|
||
{spotlightTestimonial.author || spotlightTestimonial.name}
|
||
{spotlightTestimonial.role && ` · ${spotlightTestimonial.role}`}
|
||
</footer>
|
||
)}
|
||
</blockquote>
|
||
)}
|
||
<div className="mt-auto pt-6 border-t border-white/10 flex items-center justify-between gap-6 flex-wrap">
|
||
<span className="inline-flex items-center gap-2 text-sm font-semibold text-white group-hover:text-brand-light transition-colors">
|
||
查看完整案例
|
||
<ArrowRight className="w-4 h-4 transition-transform duration-normal group-hover:translate-x-1" />
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div className="lg:w-[38%] shrink-0">
|
||
<div className="grid grid-cols-3 lg:grid-cols-1 gap-6 lg:gap-8 h-full">
|
||
{spotlightCase.metrics?.slice(0, 3).map((metric: Record<string, any>, j: number) => (
|
||
<div key={j} className={cn('lg:py-4', j < 2 && 'lg:border-b lg:border-white/10')}>
|
||
<div className={cn(
|
||
'text-3xl sm:text-4xl font-black leading-none mb-2 tabular-nums',
|
||
j === 0 ? 'text-brand-light' : 'text-white'
|
||
)}>
|
||
{metric.value}
|
||
</div>
|
||
<div className="text-sm text-white/60 font-medium leading-tight">
|
||
{metric.label}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</a>
|
||
</ScrollReveal>
|
||
)}
|
||
|
||
{restCases.length > 0 && (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
{restCases.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">
|
||
{(Array.isArray(caseItem.metrics) ? 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-ink' : '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-ink transition-colors">
|
||
<span>了解详情</span>
|
||
<ArrowRight className="w-4 h-4 transition-transform duration-normal group-hover:translate-x-1" />
|
||
</div>
|
||
</div>
|
||
</a>
|
||
</ScrollReveal>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ===== 新闻动态区(对标 Accenture News:最近 3 条 + 查看全部入口)=====
|
||
function NewsSection({ news, pageCopy }: { news?: NewsItem[]; pageCopy?: HomePageCopy | null }) {
|
||
const eyebrow = (pageCopy?.newsEyebrow as string) || '新闻动态';
|
||
const titleLines = ((pageCopy?.newsTitle as string) || '公司要闻\n与研发动态').split('\n');
|
||
const ctaLabel = (pageCopy?.newsCtaLabel as string) || '查看全部新闻';
|
||
const NEWS_ITEMS = (news?.length ? news.slice(0, 3) : NEWS.slice(0, 3)) as NewsItem[];
|
||
|
||
return (
|
||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-secondary" data-testid="news-section">
|
||
<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">
|
||
<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-ink">
|
||
{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>
|
||
<ScrollReveal delay={0.1}>
|
||
<StaticLink
|
||
href="/news"
|
||
className="group inline-flex items-center min-h-6 gap-3 text-ink font-semibold text-sm"
|
||
>
|
||
{ctaLabel}
|
||
<ArrowRight className="w-4 h-5 transition-transform duration-normal group-hover:translate-x-2 text-text-secondary group-hover:text-brand-ink" />
|
||
</StaticLink>
|
||
</ScrollReveal>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
{NEWS_ITEMS.map((item, i) => (
|
||
<ScrollReveal key={item.id} delay={i * 0.1}>
|
||
<StaticLink
|
||
href={`/news/${item.id}`}
|
||
className="group bain-card block p-8 sm:p-9 h-full flex flex-col"
|
||
>
|
||
<div className="flex items-center gap-3 mb-6">
|
||
<Badge variant="outline" className="text-xs border-brand/30 text-brand-ink">
|
||
{item.category}
|
||
</Badge>
|
||
<span className="flex items-center gap-1.5 text-xs text-text-muted">
|
||
<Calendar className="w-3.5 h-3.5" aria-hidden="true" />
|
||
{item.date}
|
||
</span>
|
||
</div>
|
||
<h3 className="text-lg lg:text-xl font-bold text-ink mb-4 leading-[1.3] line-clamp-2 group-hover:text-brand-ink transition-colors duration-normal">
|
||
{item.title}
|
||
</h3>
|
||
<p className="text-sm text-text-secondary leading-relaxed mb-6 line-clamp-2 flex-1">
|
||
{item.excerpt}
|
||
</p>
|
||
<div className="pt-4 border-t border-border-primary">
|
||
<span className="inline-flex items-center gap-2 text-sm font-semibold text-text-secondary group-hover:text-brand-ink transition-colors">
|
||
阅读全文
|
||
<ArrowRight className="w-4 h-4 transition-transform duration-normal group-hover:translate-x-1" />
|
||
</span>
|
||
</div>
|
||
</StaticLink>
|
||
</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 = canonicalCtaLabel(heroData?.ctaLabel || (pageCopy?.ctaLabel as string)) || CTA_LABELS.consult;
|
||
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 HomeContentV15({ services, cases, stats, heroData, news, pageCopy }: {
|
||
services?: Record<string, any>[];
|
||
cases?: Record<string, any>[];
|
||
stats?: Record<string, any>[];
|
||
heroData?: Record<string, any> | null;
|
||
news?: NewsItem[];
|
||
pageCopy?: HomePageCopy | null;
|
||
}) {
|
||
return (
|
||
<div>
|
||
{/* 1. Hero:浅色 editorial 画布 + 品牌红单电压(参考 Accenture Radical Relevance) */}
|
||
<HeroSection heroData={heroData} />
|
||
|
||
{/* 1.5 成果带:数据指标下沉,滚动触发(Glance 层,声明先行的承接) */}
|
||
<HeroStatsBand stats={stats} />
|
||
|
||
{/* 2. 信任区:量化数据成果展示 */}
|
||
<TrustSection pageCopy={pageCopy} />
|
||
|
||
{/* 3. 章节式叙事:问题 → 方法 → 结果 */}
|
||
<NarrativeSection pageCopy={pageCopy} />
|
||
|
||
{/* 4. 行业洞察:思想领导力支柱(对标 Accenture Insights) */}
|
||
<InsightsSection pageCopy={pageCopy} />
|
||
|
||
{/* 5. 服务区:等宽卡片 + 兜底数据 */}
|
||
<ServicesSection services={services} pageCopy={pageCopy} />
|
||
|
||
{/* 6. 创始人观点:人格化背书(对标 Accenture CEO Quote) */}
|
||
<FounderQuoteSection pageCopy={pageCopy} />
|
||
|
||
{/* 7. 案例区:轻量快照卡片 */}
|
||
<CasesSection cases={cases} pageCopy={pageCopy} />
|
||
|
||
{/* 8. 新闻动态:持续动态(对标 Accenture News) */}
|
||
<NewsSection news={news} pageCopy={pageCopy} />
|
||
|
||
{/* 9. CTA:克制强调,品牌红仅用于按钮 */}
|
||
<CTASection heroData={heroData} pageCopy={pageCopy} />
|
||
</div>
|
||
);
|
||
}
|