feat: 重构博客为水墨纸质风格 + 搭建后台管理系统

- 重新设计全站 UI:parchment/ink/terracotta 水墨纸质色系,宋式 serif 排版
- 新增页面:文章列表、文章详情、分类、标签、关于
- GSAP ScrollTrigger 滚动动画 + 逐字揭示效果
- 后台管理系统 /admin:文章/分类/标签 CRUD,JSON 文件存储
- 登录认证(cookie session)
- 设计系统文档 UI.md
This commit is contained in:
胡旭
2026-06-24 08:45:28 +08:00
parent 507f12e501
commit dce8fe62ea
35 changed files with 3124 additions and 96 deletions
+83
View File
@@ -0,0 +1,83 @@
"use client";
import { useEffect, useRef } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
interface GsapRevealProps {
children: React.ReactNode;
variant?: "fade-up" | "fade-in" | "slide-left" | "slide-right" | "scale";
delay?: number;
duration?: number;
stagger?: number;
className?: string;
once?: boolean;
}
export default function GsapReveal({
children,
variant = "fade-up",
delay = 0,
duration = 0.8,
stagger = 0,
className = "",
once = true,
}: GsapRevealProps) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!ref.current) return;
const el = ref.current;
const children = el.children.length > 1 ? el.children : [el];
const variants = {
"fade-up": { y: 40, opacity: 0 },
"fade-in": { opacity: 0 },
"slide-left": { x: -40, opacity: 0 },
"slide-right": { x: 40, opacity: 0 },
scale: { scale: 0.92, opacity: 0 },
};
const ctx = gsap.context(() => {
if (stagger > 0 && children.length > 1) {
// Each child gets its own ScrollTrigger so off-screen items animate when scrolled into view
Array.from(children).forEach((child, i) => {
gsap.from(child, {
...variants[variant],
duration,
delay: delay + i * stagger,
ease: "power3.out",
scrollTrigger: {
trigger: child,
start: "top 92%",
toggleActions: once ? "play none none none" : "play none none reverse",
},
});
});
} else {
gsap.from(children, {
...variants[variant],
duration,
delay,
ease: "power3.out",
scrollTrigger: {
trigger: el,
start: "top 88%",
toggleActions: once ? "play none none none" : "play none none reverse",
},
});
}
}, el);
return () => ctx.revert();
}, [variant, delay, duration, stagger, once]);
return (
<div ref={ref} className={className}>
{children}
</div>
);
}