style: 重做整体视觉系统(编辑设计 × 建筑精度)

- Fraunces 衬线 display + DM Sans body + JetBrains Mono 数字标签
- 暖米白主题(#f7f4ee)+ 蓝色强调色(#2563eb)
- 引入 lucide-react 图标库替代 emoji
- Sidebar 加入编号索引、Logo、用户区域、杂志风分组标题
- 产品列表页加入大号 display 标题、搜索、卡片悬浮动画
- 产品详情页面包屑、tab 编号、需求表格重做
- 状态徽章改为色点 + 文字,"已拒绝"用红色避免与"评审中"冲突
- 全局加入 grain 噪点纹理、入场交错动画

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Script Generator
2026-06-08 12:37:17 +08:00
parent 5bfa0811db
commit 0099b23a86
10 changed files with 590 additions and 240 deletions

View File

@@ -1,30 +1,66 @@
'use client';
import { Product } from '@ftb/shared';
import { Package } from 'lucide-react';
interface ProductWithCount extends Product {
interface ProductWithCount {
id: string;
name: string;
description: string;
createdAt: string | Date;
_count?: { requirements: number; projects: number };
}
interface Props {
product: ProductWithCount;
index: number;
onClick: (id: string) => void;
}
export function ProductCard({ product, onClick }: Props) {
export function ProductCard({ product, index, onClick }: Props) {
const reqCount = product._count?.requirements ?? 0;
const projCount = product._count?.projects ?? 0;
return (
<div
className="cursor-pointer rounded-lg border border-gray-200 p-5 transition-shadow hover:shadow-md"
<article
onClick={() => onClick(product.id)}
style={{ animationDelay: `${index * 60}ms` }}
className="group animate-fade-up relative cursor-pointer overflow-hidden rounded-xl border border-[var(--line)] bg-[var(--bg-elevated)] p-6 transition-all duration-300 hover:-translate-y-0.5 hover:border-[var(--ink)] hover:shadow-[0_12px_32px_-12px_rgba(26,24,20,0.18)]"
>
<h3 className="text-lg font-semibold text-gray-900">{product.name}</h3>
{product.description && (
<p className="mt-1 text-sm text-gray-500 line-clamp-2">{product.description}</p>
)}
<div className="mt-4 flex gap-4 text-xs text-gray-400">
<span>{product._count?.requirements ?? 0} </span>
<span>{product._count?.projects ?? 0} </span>
<div className="flex items-start justify-between">
<span className="font-mono text-[10px] tabular-nums uppercase tracking-[0.18em] text-[var(--ink-muted)]">
{String(index + 1).padStart(3, '0')}
</span>
<Package className="h-4 w-4 text-[var(--ink-muted)] transition-colors group-hover:text-[var(--accent)]" strokeWidth={1.75} />
</div>
</div>
<h3 className="mt-6 font-display text-[26px] font-medium leading-[1.15] tracking-tight text-[var(--ink)]">
{product.name}
</h3>
{product.description ? (
<p className="mt-2 line-clamp-2 text-[13px] leading-relaxed text-[var(--ink-soft)]">
{product.description}
</p>
) : (
<p className="mt-2 text-[13px] italic text-[var(--ink-muted)]"></p>
)}
<div className="mt-8 flex items-end justify-between border-t border-[var(--line-soft)] pt-4">
<div className="flex gap-6">
<div>
<p className="font-display text-2xl font-semibold tabular-nums text-[var(--ink)]">{reqCount}</p>
<p className="mt-0.5 font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--ink-muted)]"></p>
</div>
<div className="h-10 w-px bg-[var(--line-soft)]" />
<div>
<p className="font-display text-2xl font-semibold tabular-nums text-[var(--ink)]">{projCount}</p>
<p className="mt-0.5 font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--ink-muted)]"></p>
</div>
</div>
<span className="font-mono text-[11px] text-[var(--ink-muted)] opacity-0 transition-opacity group-hover:opacity-100">
</span>
</div>
</article>
);
}