- 拆分品牌色「文字/底色」双通道:新增 --color-brand-ink(-rgb)(深色 #F87171) 与 tailwind brand.ink;text-brand → text-brand-ink 迁移 247 处; bg-brand/border-brand 保持 --color-brand-rgb 不翻转(红底白字 5.58:1 不能动) - dark 块补齐 --color-brand-bg: #2A1418(修复深色白字压浅粉底 1.04:1) - hero 徽章新增 --badge-accent-text 桥接:accentColor 直用作深底文字不可读 (#1e3a5f 深底 1.65:1),浅色取原色 / 深色 color-mix 提亮 - 硬编码颜色 token 化:bg-white/text-gray-*/border-gray-* → bg-bg-*/text-text-*/border-border-* - 可访问性:触控目标 <24px 归零(footer 链接列 [&_a]:min-h-6 收口 377 处等)、 news 搜索框补 aria-label、标题跳级 h2→h4 修正为 h3、装饰 blur 光斑容器补 overflow-hidden 消除伪文本裁剪 - 验证:tsc 0 / eslint 0 error / jest 0 失败用例 / dogfood 33 路由 × 双主题 P1-P3 全零
183 lines
6.1 KiB
TypeScript
183 lines
6.1 KiB
TypeScript
'use client';
|
||
|
||
/**
|
||
* L3SignalsSlot — L3 信任层「替代信号」槽
|
||
*
|
||
* 设计意图:
|
||
* - 真实数据(客户案例 / 资质认证 / 数据佐证 / 证言)需获得客户授权后才能公开;
|
||
* 在那之前,详情页 L3 必须有可验证的兜底内容(避免 L3 整段消失 = 信任真空)。
|
||
* - 四类替代信号(方法论透明度 / 团队资历 / 里程碑 / 服务承诺)均为站点内
|
||
* 可访问的真实路由(/methodology · /team · /about/brand · /contact),
|
||
* 不存在编造。
|
||
*
|
||
* 接入位置:详情页 L3 区块(案例 / 认证 / 数据)之前,由 L3EmptyFallback 在三者
|
||
* 全空时统一包裹渲染;任一非空时本组件不渲染,让真实 section 自行展示。
|
||
*
|
||
* 设计约束(CLAUDE.md / CONTEXT.md):
|
||
* - 品牌红 ≥3 处触达点(eyebrow 短线 + Icon 容器 + 卡片 hover 边)
|
||
* - 动效 ≤700ms ease-ink(ScrollReveal/StaggerReveal 默认 400ms,符合"动效四原则 fast")
|
||
* - WCAG AA:text-secondary 在 bg-primary 4.5:1+;hover 状态焦点环保留
|
||
*
|
||
* @see /Users/zhangxiang/Codes/Novalon/novalon-website/.workbuddy/memory/MEMORY.md
|
||
* "内容真实性约束"(零编造前提下的 L3 替代信号)
|
||
*/
|
||
|
||
import Link from 'next/link';
|
||
import {
|
||
ArrowUpRight,
|
||
Compass,
|
||
Milestone,
|
||
ShieldCheck,
|
||
Users,
|
||
} from 'lucide-react';
|
||
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
||
import { SectionLabel } from '@/components/ui/page-decoration';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
interface L3Signal {
|
||
href: string;
|
||
eyebrow: string;
|
||
title: string;
|
||
desc: string;
|
||
Icon: React.ComponentType<{ className?: string }>;
|
||
/**
|
||
* 服务承诺卡:文案散落 5 处但无可复用 L3 section,
|
||
* 待业务确认后才有具体承诺条款。当前显示「待业务确认」占位(无 href 跳转)。
|
||
*/
|
||
disabled?: boolean;
|
||
}
|
||
|
||
const SIGNALS: readonly L3Signal[] = [
|
||
{
|
||
href: '/methodology',
|
||
eyebrow: '方法论',
|
||
title: '交付方法论',
|
||
desc: '从诊断评估到持续优化,四阶段可量化路径;不依赖案例也能证明过程可控。',
|
||
Icon: Compass,
|
||
},
|
||
{
|
||
href: '/team',
|
||
eyebrow: '团队',
|
||
title: '团队资历',
|
||
desc: '创始团队与核心成员的背景、承诺宣言与协作方式。',
|
||
Icon: Users,
|
||
},
|
||
{
|
||
href: '/about/brand',
|
||
eyebrow: '历程',
|
||
title: '发展历程',
|
||
desc: '2026 年 1 月成立至今的关键节点与里程碑。',
|
||
Icon: Milestone,
|
||
},
|
||
{
|
||
href: '/contact',
|
||
eyebrow: '承诺',
|
||
title: '服务承诺',
|
||
desc: '具体承诺条款待业务确认后公开;当前阶段以共创协议为准,欢迎垂询。',
|
||
Icon: ShieldCheck,
|
||
disabled: true,
|
||
},
|
||
] as const;
|
||
|
||
export function L3SignalsSlot({
|
||
heading = '在数据之外,我们用透明度建立信任',
|
||
description = '客户案例与资质认证需获得授权后才能公开;在那之前,以下四个锚点是我们可验证的承诺。',
|
||
}: {
|
||
heading?: string;
|
||
description?: string;
|
||
}) {
|
||
return (
|
||
<section
|
||
aria-labelledby="l3-signals-heading"
|
||
className="relative py-16 sm:py-20 md:py-24 overflow-hidden bg-bg-secondary"
|
||
>
|
||
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
||
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
|
||
|
||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
|
||
<ScrollReveal className="text-center max-w-3xl mx-auto mb-12">
|
||
<SectionLabel align="center">信任锚点</SectionLabel>
|
||
<h2
|
||
id="l3-signals-heading"
|
||
className="text-2xl sm:text-3xl md:text-4xl font-bold text-ink tracking-tight leading-tight"
|
||
>
|
||
{heading}
|
||
</h2>
|
||
<p className="mt-4 text-base text-text-secondary leading-relaxed">{description}</p>
|
||
</ScrollReveal>
|
||
|
||
{/* grid 类直接挂在 StaggerReveal 上:StaggerReveal 会为每个子项包一层 motion.div,
|
||
若把 grid 放在外层容器,包装层会成为 grid 的唯一子项,卡片将堆成单列 */}
|
||
<StaggerReveal
|
||
staggerDelay={0.06}
|
||
delayChildren={0.05}
|
||
className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6"
|
||
>
|
||
{SIGNALS.map((s) => (
|
||
<SignalCard key={s.eyebrow} signal={s} />
|
||
))}
|
||
</StaggerReveal>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
function SignalCard({ signal }: { signal: L3Signal }) {
|
||
const { href, eyebrow, title, desc, Icon, disabled } = signal;
|
||
|
||
const cardBody = (
|
||
<div
|
||
className={cn(
|
||
'group relative h-full p-6 sm:p-8 rounded-lg border border-border-primary bg-bg-primary',
|
||
'transition-all duration-300 ease-out',
|
||
disabled
|
||
? 'opacity-70 cursor-not-allowed'
|
||
: 'hover:border-brand/40 hover:shadow-md hover:-translate-y-1'
|
||
)}
|
||
>
|
||
<span className="inline-block text-[11px] tracking-[0.15em] uppercase font-bold text-brand-ink mb-4">
|
||
{eyebrow}
|
||
</span>
|
||
|
||
<div
|
||
className="w-10 h-10 mb-4 flex items-center justify-center rounded-md bg-brand-soft text-brand-ink"
|
||
aria-hidden="true"
|
||
>
|
||
<Icon className="w-5 h-5" />
|
||
</div>
|
||
|
||
<h3 className="text-base font-bold text-ink mb-2 flex items-center gap-1.5">
|
||
{title}
|
||
{!disabled && (
|
||
<ArrowUpRight
|
||
className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
|
||
aria-hidden="true"
|
||
/>
|
||
)}
|
||
</h3>
|
||
|
||
<p className="text-sm text-text-secondary leading-relaxed">{desc}</p>
|
||
|
||
{disabled && (
|
||
<p className="mt-4 text-xs text-text-muted italic">待业务确认</p>
|
||
)}
|
||
</div>
|
||
);
|
||
|
||
if (disabled) {
|
||
return (
|
||
<div aria-disabled="true" className="block rounded-lg">
|
||
{cardBody}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Link
|
||
href={href}
|
||
className="block rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2 focus-visible:ring-offset-bg-secondary"
|
||
>
|
||
{cardBody}
|
||
</Link>
|
||
);
|
||
} |