Files
novalon-website/src/components/detail/detail-certification.tsx
T
zhangxiang 611b6b87fe feat(ui): 营销站 UI/UE/UX 治理 P0→P3 收官
复评(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
2026-09-20 09:17:07 +08:00

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>
);
}