Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 49 additions & 27 deletions frontend/src/app/(blog)/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,25 @@ import { getAbout, type AboutInfo } from "@/lib/about";
import { useIsAdmin } from "@/hooks/useIsAdmin";
import { AboutSidebar } from "@/components/about/AboutSidebar";
import { AboutMain } from "@/components/about/AboutMain";
import { BannerUploader } from "@/components/about/BannerUploader";
import { Skeleton } from "@/components/ui/skeleton";

/**
* 关于页:左侧个人卡片 + 右侧主内容。
* 关于页:
* ┌────────────────────────────────────────────┐
* │ Hero Banner(mesh + 粒子) │
* ├──────────────┬─────────────────────────────┤
* │ Sidebar │ Main(About Me / Skills / │
* │ - Identity │ Bio) │
* │ - 联系方式 │ │
* │ - Tech Stack│ │
* └──────────────┴─────────────────────────────┘
*
* - 公共部分(头像、昵称、bio、tech stack 等)所有人都能看到;
* - 「编辑资料」按钮和头像/Banner 的 hover 上传交互仅对管理员暴露,
* 借 useIsAdmin hook 判断(token 存在即可编辑)。
* 两栏 grid 用 -mt-16 上移,让身份卡里的 -mt-12 头像正好叠到 banner 下沿,
* 跟 mockup 的「头像压住两块」视觉一致。
*
* - 公共部分(昵称、bio、tech stack 等)所有人都能看到;
* - 头像 / Banner hover 上传、「编辑资料」按钮仅对管理员暴露。
*
* 注意:头像/Banner 上传后只更新本地预览,不会自动落库——落库需要
* 管理员在「站点设置」页点击「保存」按钮(avatar/banner 已合并进表单)。
Expand Down Expand Up @@ -48,42 +59,53 @@ export default function AboutPage() {

if (loading) {
return (
<div className="grid gap-8 md:grid-cols-[280px_1fr]">
<div className="space-y-3">
<Skeleton className="mx-auto h-36 w-36 rounded-full" />
<Skeleton className="mx-auto h-6 w-32" />
<Skeleton className="mx-auto h-4 w-48" />
<Skeleton className="h-24 w-full" />
</div>
<div className="space-y-4">
<Skeleton className="h-32 w-full" />
<Skeleton className="h-48 w-full" />
<Skeleton className="h-32 w-full" />
<div className="space-y-6">
{/* Banner 骨架 */}
<Skeleton className="h-[180px] w-full rounded-2xl md:h-[200px]" />
{/* 两栏骨架 */}
<div className="grid gap-6 lg:grid-cols-[280px_1fr]">
<div className="space-y-4">
<Skeleton className="h-44 w-full rounded-xl" />
<Skeleton className="h-24 w-full rounded-xl" />
<Skeleton className="h-32 w-full rounded-xl" />
</div>
<div className="space-y-4">
<Skeleton className="h-40 w-full rounded-xl" />
<Skeleton className="h-56 w-full rounded-xl" />
<Skeleton className="h-32 w-full rounded-xl" />
</div>
</div>
</div>
);
}

if (!data) {
return (
<div className="py-20 text-center text-slate-500">暂无关于信息</div>
<div className="py-20 text-center text-muted-foreground">
暂无关于信息
</div>
);
}

return (
<div className="grid gap-10 md:grid-cols-[280px_1fr]">
<AboutSidebar
data={data}
<div className="space-y-6">
{/* Hero Banner */}
<BannerUploader
value={bannerDraft}
onChange={setBannerDraft}
editable={isAdmin}
avatarValue={avatarDraft}
onAvatarChange={setAvatarDraft}
/>
<AboutMain
data={data}
editable={isAdmin}
bannerValue={bannerDraft}
onBannerChange={setBannerDraft}
/>

{/* 两栏:-mt-16 让身份卡里的头像(-mt-12)正好叠到 banner 下沿 */}
<section className="relative z-10 grid items-start gap-6 lg:-mt-16 lg:grid-cols-[280px_1fr]">
<AboutSidebar
data={data}
editable={isAdmin}
avatarValue={avatarDraft}
onAvatarChange={setAvatarDraft}
/>
<AboutMain data={data} />
</section>
</div>
);
}
151 changes: 151 additions & 0 deletions frontend/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,157 @@
border-color: rgba(239, 68, 68, 0.3);
}

/* ===== 关于页:Mesh Banner / 粒子 / 侧栏小标题 / Tech 图标 ===== */
.banner-mesh {
background-color: #1e3a8a;
background-image:
radial-gradient(at 18% 24%, rgba(59, 130, 246, 0.85) 0px, transparent 55%),
radial-gradient(at 82% 18%, rgba(99, 102, 241, 0.7) 0px, transparent 55%),
radial-gradient(at 72% 78%, rgba(96, 165, 250, 0.6) 0px, transparent 55%),
linear-gradient(135deg, #1e3a8a 0%, #2563eb 50%, #1e40af 100%);
}
.banner-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
background-size: 60px 60px;
-webkit-mask-image: radial-gradient(ellipse at center, black 25%, transparent 75%);
mask-image: radial-gradient(ellipse at center, black 25%, transparent 75%);
pointer-events: none;
}
.banner-grid::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5), transparent);
}

@keyframes drift {
0%,
100% {
transform: translateY(0);
opacity: 0.5;
}
50% {
transform: translateY(-10px);
opacity: 1;
}
}
.particle {
animation: drift 6s ease-in-out infinite;
}

@keyframes rise {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: none;
}
}
.reveal-up {
animation: rise 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* 侧栏小标题:左竖条 + 文字 */
.side-title {
font-size: 0.875rem;
font-weight: 600;
color: var(--foreground);
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.side-title::before {
content: "";
width: 0.25rem;
height: 1rem;
border-radius: 9999px;
background: var(--primary);
flex-shrink: 0;
}

.link-brand {
color: var(--muted-foreground);
transition: color 150ms ease;
}
.link-brand:hover {
color: var(--primary);
}

/* Tech Stack 32×32 小图标 */
.tech-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 8px;
color: #fff;
font-weight: 700;
font-size: 11px;
font-family: var(--font-mono, ui-monospace, monospace);
box-shadow: 0 4px 10px -3px rgba(0, 0, 0, 0.18);
transition: transform 0.15s ease;
cursor: default;
}
.tech-icon:hover {
transform: translateY(-2px);
}

/* 头像环:内白 + 外品牌蓝 */
.avatar-ring {
box-shadow:
0 0 0 4px var(--card),
0 0 0 6px var(--primary),
0 8px 24px -6px rgba(0, 0, 0, 0.18);
}
/* 头像占位:品牌渐变 + 首字母 */
.avatar-placeholder {
background: linear-gradient(135deg, #60a5fa 0%, #2563eb 50%, #1e3a8a 100%);
color: #fff;
font-weight: 700;
letter-spacing: -0.02em;
text-transform: lowercase;
}

/* 身份卡:上方贴出大头像的「口袋」容器 */
/* Card 默认 overflow-hidden + hover 上移;
身份卡既要可见(让 -mt-12 的头像不被裁),又不能 hover 上移(避免头像视觉抖动)。 */
.card-static {
overflow: visible !important;
}
.card-static:hover {
--tw-translate-y: 0 !important;
translate: none !important;
}

/* Banner 标签条(pill 样式) */
.banner-pill {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.5rem;
border-radius: 0.25rem;
font-size: 11px;
line-height: 1.4;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}

@media (prefers-reduced-motion: reduce) {
.particle,
.reveal-up {
animation: none;
}
}

/* ===== DataTable 状态徽章(spec §5.13) ===== */
.status-badge {
display: inline-flex;
Expand Down
Loading
Loading