feat(design): Vibe design optimization — Hero, Bento grids, trust layer, narrative
升级全站体验:首页 Hero 改为产品视觉 + 单一 CTA 转化布局;产品矩阵/服务/方案改 为 Bento 非对称网格(ERP 2x2 大卡 + BI 1x2);补齐信任层(可验证信号 + 来源标注) 与「问题→方法→结果」章节式叙事;新增 hero-product-visual 与 bento-grid 可复用组件; 统一动效与品牌视觉资产。新增对应单测、UJ-11 用户旅程测试并更新视觉回归基线快照。
This commit is contained in:
@@ -5,10 +5,11 @@ 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 { 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 } from '@/lib/constants';
|
||||
import { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '@/lib/constants';
|
||||
|
||||
// ===== 服务图标映射:将字符串键名映射为 Lucide 组件 =====
|
||||
const SERVICE_ICON_MAP: Record<string, React.ReactNode> = {
|
||||
@@ -18,7 +19,7 @@ const SERVICE_ICON_MAP: Record<string, React.ReactNode> = {
|
||||
Puzzle: <Puzzle className="w-7 h-7" />,
|
||||
};
|
||||
|
||||
// ===== 服务数据降级兜底 =====
|
||||
// ===== 服务数据降级兜底(4 条,保证 sm:grid-cols-2 网格完整,避免孤儿格)=====
|
||||
const FALLBACK_SERVICES = [
|
||||
{
|
||||
subtitle: 'Strategy Consulting',
|
||||
@@ -29,9 +30,21 @@ const FALLBACK_SERVICES = [
|
||||
{ value: '40%', label: '平均运营效率提升' },
|
||||
{ value: '6个月', label: '战略到试点周期' },
|
||||
],
|
||||
href: '/services/strategy',
|
||||
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 应用',
|
||||
@@ -53,21 +66,23 @@ const FALLBACK_SERVICES = [
|
||||
{ value: '6', label: '行业场景覆盖' },
|
||||
{ value: '99.9%', label: '系统可用性' },
|
||||
],
|
||||
href: '/services/solution',
|
||||
href: '/services/solutions',
|
||||
icon: <Layers className="w-7 h-7" />,
|
||||
},
|
||||
];
|
||||
|
||||
// ===== 品牌 CTA 按钮 =====
|
||||
function CTAButton({ children, href, variant = 'primary', className }: {
|
||||
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'
|
||||
@@ -93,8 +108,8 @@ function HeroSection({ heroData, stats }: {
|
||||
const subheading = heroData?.subheading || heroData?.description || '';
|
||||
const ctaLabel = heroData?.ctaLabel || '预约咨询';
|
||||
const ctaHref = heroData?.ctaHref || '/contact';
|
||||
const secondaryCtaLabel = heroData?.secondaryCtaLabel || '';
|
||||
const secondaryCtaHref = heroData?.secondaryCtaHref || '#';
|
||||
const secondaryCtaLabel = heroData?.secondaryCtaLabel || '查看产品矩阵';
|
||||
const secondaryCtaHref = heroData?.secondaryCtaHref || '/products';
|
||||
|
||||
const hasValidStats = stats?.length && stats[0]?.value;
|
||||
const displayStats = hasValidStats ? stats!.slice(0, 3) : [];
|
||||
@@ -112,14 +127,15 @@ function HeroSection({ heroData, stats }: {
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-24 sm:py-32 md:py-40 lg:py-48">
|
||||
<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.6, delay: 0, ease: EASE_OUT }}
|
||||
className="mb-8"
|
||||
transition={{ duration: 0.35, delay: 0, ease: EASE_OUT }}
|
||||
className="mb-6"
|
||||
>
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
@@ -135,8 +151,8 @@ function HeroSection({ heroData, stats }: {
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.1, ease: EASE_OUT }}
|
||||
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-black text-ink leading-[1.05] tracking-tight mb-10"
|
||||
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>
|
||||
@@ -146,12 +162,13 @@ function HeroSection({ heroData, stats }: {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.3, ease: EASE_OUT }}
|
||||
className="flex flex-wrap gap-8 sm:gap-12 md:gap-16 mb-10"
|
||||
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="text-3xl sm:text-4xl md:text-5xl font-black text-brand tabular-nums leading-none">
|
||||
<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">
|
||||
@@ -167,8 +184,8 @@ function HeroSection({ heroData, stats }: {
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.4, ease: EASE_OUT }}
|
||||
className="text-lg sm:text-xl text-text-secondary max-w-2xl leading-relaxed mb-12"
|
||||
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>
|
||||
@@ -178,13 +195,14 @@ function HeroSection({ heroData, stats }: {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.5, ease: EASE_OUT }}
|
||||
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}>{ctaLabel}</CTAButton>
|
||||
<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}
|
||||
@@ -193,22 +211,26 @@ function HeroSection({ heroData, stats }: {
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
// ===== 信任/成果区:Bain 式量化数据展示 =====
|
||||
// ===== 信任/成果区:可验证信任信号 + 首批客户共创标签 =====
|
||||
function TrustSection() {
|
||||
const items = [
|
||||
{ value: '2026', label: '年正式成立', desc: '1 月 15 日于成都龙泉驿区启航' },
|
||||
{ value: '10+', label: '人核心团队', desc: '复合型技术团队,覆盖咨询与研发' },
|
||||
{ value: '6', label: '款自研产品', desc: '覆盖核心数字化业务场景' },
|
||||
{ value: '100%', label: '私有化部署', desc: '核心数据不出境,满足合规要求' },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-white">
|
||||
<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">
|
||||
@@ -224,15 +246,113 @@ function TrustSection() {
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{items.map((item, i) => (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-10">
|
||||
{TRUST_SIGNALS.map((item, i) => (
|
||||
<StaggerReveal key={i} staggerDelay={0.06}>
|
||||
<div className="bain-card p-8 sm:p-10">
|
||||
<div className="text-3xl sm:text-4xl font-black text-brand tabular-nums leading-none mb-2">
|
||||
{item.value}
|
||||
<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="flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-6 p-6 sm:p-8 bg-brand-bg border border-[rgba(196,30,58,0.15)]" data-testid="early-access-banner">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
{EARLY_ACCESS.label}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-text-secondary leading-relaxed">
|
||||
{EARLY_ACCESS.desc}
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="/contact"
|
||||
className="group inline-flex shrink-0 items-center gap-2 text-sm font-semibold text-ink hover:text-brand transition-colors sm:ml-auto"
|
||||
>
|
||||
<span>成为首批共创客户</span>
|
||||
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
|
||||
</a>
|
||||
</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() {
|
||||
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]">
|
||||
从问题到结果,
|
||||
<br />
|
||||
一条可量化的路径
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-px bg-border-primary">
|
||||
{NARRATIVE_ACTS.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 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 className="text-sm font-semibold text-ink mb-3">{item.label}</div>
|
||||
<p className="text-sm text-text-secondary leading-relaxed">{item.desc}</p>
|
||||
</div>
|
||||
</StaggerReveal>
|
||||
))}
|
||||
@@ -265,7 +385,7 @@ function ServicesSection({ services }: { services?: Record<string, any>[] }) {
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<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">
|
||||
@@ -291,8 +411,8 @@ function ServicesSection({ services }: { services?: Record<string, any>[] }) {
|
||||
<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-brand leading-none mb-1">{m.value}</div>
|
||||
<div className="text-[11px] text-text-muted font-medium">{m.label}</div>
|
||||
<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>
|
||||
@@ -320,9 +440,43 @@ function CasesSection({ cases }: { cases?: Record<string, any>[] }) {
|
||||
|
||||
if (CASE_ITEMS.length === 0) {
|
||||
return (
|
||||
<section className="relative py-28 overflow-hidden bg-white">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 text-center">
|
||||
<p className="text-text-muted text-lg">暂无案例数据</p>
|
||||
<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">
|
||||
共创进行时
|
||||
</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">
|
||||
每一个项目,
|
||||
<br />
|
||||
都以业务改善为衡量标准
|
||||
</h2>
|
||||
<p className="text-text-secondary leading-relaxed text-lg mb-8">
|
||||
我们正与首批客户共同打磨产品与方案,以真实运营数据验证业务改善。
|
||||
<br className="hidden sm:block" />
|
||||
公开案例将在验证后发布——合作成果,经得起衡量。
|
||||
</p>
|
||||
<a
|
||||
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" />
|
||||
</a>
|
||||
</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">
|
||||
{EARLY_ACCESS.label}
|
||||
</div>
|
||||
<p className="text-text-secondary leading-relaxed">{EARLY_ACCESS.desc}</p>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
@@ -436,13 +590,16 @@ export default function HomeContentV14({ services, cases, stats, heroData }: {
|
||||
{/* 2. 信任区:量化数据成果展示 */}
|
||||
<TrustSection />
|
||||
|
||||
{/* 3. 服务区:等宽卡片 + 兜底数据 */}
|
||||
{/* 3. 章节式叙事:问题 → 方法 → 结果 */}
|
||||
<NarrativeSection />
|
||||
|
||||
{/* 4. 服务区:等宽卡片 + 兜底数据 */}
|
||||
<ServicesSection services={services} />
|
||||
|
||||
{/* 4. 案例区:轻量快照卡片 */}
|
||||
{/* 5. 案例区:轻量快照卡片 */}
|
||||
<CasesSection cases={cases} />
|
||||
|
||||
{/* 5. CTA:克制强调,品牌红仅用于按钮 */}
|
||||
{/* 6. CTA:克制强调,品牌红仅用于按钮 */}
|
||||
<CTASection heroData={heroData} />
|
||||
</main>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user