复评(impeccable critique)全部 Priority Issues 与 Minor Observations 落地: - P0 数字口径:MetricsBasisNote 单一真源 + metrics-basis.test fs 防线,接入 products/solutions/services/home/about;纯渲染端加角注,不动 DB/seed 值 - P1 转化断头路:敬请期待态 + 404 语境出口 + services 空态双出口 - P1 首页并卡:TrustSection 早鸟横幅并入 CasesSection 单张深色共创卡 - P2 CTA 治理:CONSULT_CTA_ALLOWLIST 白名单 + 逐字符扫描器,/contact CTA 全归一 - P3 polish:footer 补 服务/公司列、contact 死三元/空槽/toast、GlobalErrorTracker 幂等日志 - 文档同源:PRODUCT/CONTEXT 动效带统一 180–280ms;DESIGN.md 立 The 10px Floor; font-floor.test 守卫禁止再引入 <10px(保留 10/11px 有意 Bain 微标签) Gates: type-check 0 · jest 1581/132 · lint 0e/126w · contrast 7/7 · headings 10/10
48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
'use client';
|
|
|
|
import { motion } from 'framer-motion';
|
|
import { Award, ExternalLink } from 'lucide-react';
|
|
import type { Certification } from '@/lib/constants/products';
|
|
|
|
interface CertificationListProps {
|
|
certifications: Certification[];
|
|
}
|
|
|
|
export function CertificationList({ certifications }: CertificationListProps) {
|
|
if (certifications.length === 0) return null;
|
|
|
|
return (
|
|
<div className="flex flex-wrap gap-3">
|
|
{certifications.map((cert, index) => {
|
|
const link = cert.link && cert.link !== '#' ? cert.link : null;
|
|
const className =
|
|
'inline-flex items-center gap-2 rounded-full border border-border-primary bg-bg-elevated px-4 py-2 text-sm text-text-secondary shadow-sm transition-all hover:border-border-secondary hover:shadow-md';
|
|
const motionProps = {
|
|
initial: { opacity: 0, scale: 0.95 },
|
|
whileInView: { opacity: 1, scale: 1 },
|
|
viewport: { once: true, margin: '-50px' },
|
|
transition: { duration: 0.3, delay: index * 0.05 },
|
|
className,
|
|
};
|
|
const inner = (
|
|
<>
|
|
<Award className="h-4 w-4 text-brand-ink" />
|
|
<span className="font-medium">{cert.name}</span>
|
|
<span className="text-xs text-text-muted">{cert.issuer}</span>
|
|
{link && <ExternalLink className="h-3 w-3 text-text-muted" />}
|
|
</>
|
|
);
|
|
return link ? (
|
|
<motion.a key={cert.name} href={link} {...motionProps}>
|
|
{inner}
|
|
</motion.a>
|
|
) : (
|
|
<motion.span key={cert.name} {...motionProps}>
|
|
{inner}
|
|
</motion.span>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|